Codebase list python-faraday / upstream/3.1
New upstream version 3.1 Sophie Brun 5 years ago
67 changed file(s) with 9342 addition(s) and 3935 deletion(s). Raw diff Collapse all Expand all
1414 * Matias Lang
1515 * Micaela Ranea Sánchez
1616 * Sebastian Kulesz
17 * Eric Horvat
1718
1819 Project contributors
1920
66
77 New features in the latest update
88 =====================================
9
10 September 17, 2018:
11 ---
12 * Fix get exploits API
13 * New searcher feature
14 * Added host_os column to status report
15 * Fix and error while trying to execute server with --start
16 * Added option --choose-password to initdb
17 * Continous scan updated for Nessus 7
18 * Refactor on server.config to remove globals
19 * Added a directory for custom templates for executive reports (pro and corp)
20 * Activity feed shows more results and allows to filter empty results
21 * Allow ot create workspace that start with numbers
22 * Added more variables to executive reports (pro and corp)
23 * Fixed some value checking on tasks api (date field)
24 * OpenVas plugin updated
25 * Appscan plugin update
26 * Added no confirmed vulns to report api
27 * Fixed a bug on workspace API when the workspace already exists on database
28 * Fix owner filter on status report
29 * Fixes on import_csv fplugin when the api returned 409
30 * Fixes on status_check
31 * Fixed a bug on webui when workspace permission was changed (pro and corp)
32 * Update nexpose plugin
33 * uigrid library updated to latest version
34 * Bug fix on plugin automatic detection
35 * Fixed a bug on executive reports when multiple reports were scheduled
36 * Avoid closing the executive report and new vuln modal when the form has data
37 * Status report open new tab for evidence
38 * added change_password to manage.py
39 * Update wapiti plugin
40 * Fixed vuln count on executive report (pro and corp)
41 * Fixed css align in some tables
42 * Fixed No ports available error on the client
943
1044 August 17, 2018:
1145 ---
0 3.0.1
0 3.1
5050
5151 ioloop_instance = IOLoop.current()
5252 _http_server = HTTPServer(WSGIContainer(app))
53 while True:
53 hostnames = [hostname]
54
55 #Fixed hostname bug
56 if hostname == "localhost":
57
58 hostnames.append("127.0.0.1")
59 print hostname
60
61 listening = False
62 for hostname in hostnames:
5463 try:
5564 _http_server.listen(port, address=hostname)
5665 logger.getLogger().info(
5766 "REST API server configured on %s" % str(
5867 CONF.getApiRestfulConInfo()))
68 listening = True
69 CONF.setApiConInfoHost(hostname)
70 CONF.saveConfig()
5971 break
6072 except socket.error as exception:
61 if exception.errno == 98:
62 # Port already in use
63 # Let's try the next one
64 port += 1
65 if port > 65535:
66 raise Exception("No ports available!")
67 CONF.setApiRestfulConInfoPort(port)
68 CONF.saveConfig()
69 else:
70 raise exception
73 continue
74 if not listening:
75 raise RuntimeError("Port already in use")
7176
7277 routes = [r for c in _rest_controllers for r in c.getRoutes()]
7378
297297 'parent_type': parent_type,
298298 'parent': parent_id,
299299 }
300 counter += 1
301 print "New vulnerability: " + vulnerability.getName()
302 models.create_vuln(WORKSPACE, vulnerability)
300
301 if not models.get_vuln(WORKSPACE, **vuln_params):
302 counter += 1
303 print "New vulnerability: " + vulnerability.getName()
304 models.create_vuln(WORKSPACE, vulnerability)
303305
304306 elif vulnerability_web is not None:
305307
11 <faraday>
22
33 <appname>Faraday - Penetration Test IDE</appname>
4 <version>3.0</version>
4 <version>3.1</version>
55 <debug_status>0</debug_status>
66 <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font>
77 <home_path>~/</home_path>
139139
140140 else:
141141 if not args.port:
142 args.port = 5985
142 args.port = '5985'
143143
144144 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
145145 result = sock.connect_ex((args.bind_address or server.config.faraday_server.bind_address, int(args.port or server.config.faraday_server.port)))
7676 def get_host(self, host_id):
7777 return models.get_host(self.active_workspace, host_id)
7878
79 @safe_io_with_server((0, 0, 0, 0))
79 @safe_io_with_server((0, 0, 0))
8080 def get_workspace_numbers(self):
8181 return models.get_workspace_numbers(self.active_workspace)
8282
1818 from server.commands.app_urls import show_all_urls
1919 from server.commands.reports import import_external_reports
2020 from server.commands import status_check as status_check_functions
21 from server.commands import change_password as change_pass
2122 from server.models import db, User
2223 from server.importer import ImportCouchDB
2324
6970 show_all_urls()
7071
7172 @click.command(help="Create Faraday DB in Postgresql, also tables and indexes")
72 def initdb():
73 with app.app_context():
74 InitDB().run()
73 @click.option(
74 '--choose-password', is_flag=True, default=False,
75 help=('Instead of using a random password for the user "faraday", '
76 'ask for the desired one')
77 )
78 def initdb(choose_password):
79 with app.app_context():
80 InitDB().run(choose_password=choose_password)
7581 couchdb_config_present = server.config.couchdb
7682 if couchdb_config_present and couchdb_config_present.user and couchdb_config_present.password:
7783 print('Importing data from CouchDB, please wait...')
111117 selected = False
112118 exit_code = 0
113119 if check_postgresql:
114 # exit_code is created for Faraday automation-testing purposes
120 # exit_code was created for Faraday automation-testing purposes
115121 exit_code = status_check_functions.print_postgresql_status()
116122 status_check_functions.print_postgresql_locks_status()
117 selected = True
123 selected = True
118124
119125 if check_faraday:
120126 status_check_functions.print_faraday_status()
132138 status_check_functions.full_status_check()
133139
134140 sys.exit(exit_code)
141
142 @click.command(help="Changes the password of a user")
143 def change_password():
144 username = raw_input("Enter the Name of the User: ")
145 password = raw_input("Enter the new password: ")
146 if password is None or password == "":
147 print "Invalid password"
148 exit
149 change_pass.changes_password(username, password)
135150
136151 def validate_user_unique_field(ctx, param, value):
137152 with app.app_context():
156171 def createsuperuser(username, email, password):
157172 with app.app_context():
158173 if db.session.query(User).filter_by(active=True).count() > 0:
159 print("Can't create more users. Please contact support")
174 print("Can't create more users. The comumunity edition only allows one user. Please contact support for further information.")
160175 sys.exit(1)
176
161177 app.user_datastore.create_user(username=username,
162178 email=email,
163179 password=password,
192208 cli.add_command(sql_shell)
193209 cli.add_command(status_check)
194210 cli.add_command(create_tables)
211 cli.add_command(change_password)
195212
196213
197214 if __name__ == '__main__':
222222 add the file signature here
223223 and add the code in self.getRootTag() for get the root tag.
224224 """
225
226225 f = result = None
227226
228227 signatures = {
238237
239238 f = open(file_path, 'rb')
240239 file_signature = f.read(10)
241 f.seek(0)
242240
243241 for key in signatures:
244242 if file_signature.find(key) == 0:
249247 if not result:
250248 # try json loads to detect a json file.
251249 try:
250 f.seek(0)
252251 json.loads(f.read())
253 f.seek(0)
254252 result = 'json'
255253 except ValueError:
256254 pass
261259 "Error while opening file.\n%s. %s" % (err, file_path))
262260
263261 getLogger(self).debug("Report type detected: %s" % result)
262 f.seek(0)
264263 return f, result
265264
266265 def getRootTag(self, file_path):
6868 if _xmlrpc_api_server is None:
6969 #TODO: some way to get defaults.. from config?
7070 if str(hostname) == "None":
71 hostname = "localhost"
71 hostname = "127.0.0.1"
7272 if str(port) == "None":
7373 port = 9876
7474
7676 CONF.setApiConInfo(hostname, port)
7777 devlog("starting XMLRPCServer with api_conn_info = %s" % str(CONF.getApiConInfo()))
7878
79 while True:
79 hostnames = [hostname]
80 if hostname == "localhost":
81 hostnames.append("127.0.0.1")
82
83 listening = False
84 for hostname in hostnames:
8085
8186 try:
82 _xmlrpc_api_server = model.common.XMLRPCServer(CONF.getApiConInfo())
87 _xmlrpc_api_server = model.common.XMLRPCServer((hostname,CONF.getApiConInfoPort()))
8388 # Registers the XML-RPC introspection functions system.listMethods, system.methodHelp and system.methodSignature.
8489 _xmlrpc_api_server.register_introspection_functions()
8590
103108 _xmlrpc_api_server.register_function(devlog)
104109
105110 #TODO: check if all necessary APIs are registered here!!
111 listening = True
112 CONF.setApiConInfo(hostname, port)
113 CONF.saveConfig()
106114
107115 getLogger().info(
108116 "XMLRPC API server configured on %s" % str(
109117 CONF.getApiConInfo()))
110118 break
111 except socket.error as exception:
112 if exception.errno == 98:
113 # Port already in use
114 # Let's try the next one
115 port += 1
116 if port > 65535:
117 raise Exception("No ports available!")
118 CONF.setApiConInfo(hostname, port)
119 CONF.saveConfig()
120 elif exception.errno == 48:
121 # Address already open
122 # Another instance of faraday.py already running
123 raise Exception("Another instance of faraday.py already running!")
124 else:
125 raise exception
126 except Exception as e:
127 msg = "There was an error creating the XMLRPC API Server:\n%s" % str(e)
119
120 except socket.error as e:
121 msg = "There was an error creating the XMLRPC API Server (Host:{}): {}".format(hostname,e)
128122 log(msg)
129 devlog("[ERROR] - %s" % msg)
130
123 devlog("[WARNING] - %s" % msg)
124
125 if not listening:
126 raise RuntimeError("Port already in use")
131127
132128 #-------------------------------------------------------------------------------
133129 # APIs to create and add elements to model
255255 self.__addPendingAction(Modelactions.ADDSERVICEHOST, serv_obj)
256256 return serv_obj.getID()
257257
258 def createAndAddVulnToHost(self, host_id, name, data="", desc="", ref=[],
259 severity="", resolution=""):
258 def createAndAddVulnToHost(self, host_id, name, desc="", ref=[],
259 severity="", resolution="", data=""):
260260
261261 vuln_obj = model.common.factory.createModelObject(
262262 Vuln.class_signature,
272272 @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5",
273273 current_version=VERSION,
274274 details="Interface object removed. Use host or service instead. Vuln will be added to Host")
275 def createAndAddVulnToInterface(self, host_id, interface_id, name, data="",
275 def createAndAddVulnToInterface(self, host_id, interface_id, name,
276276 desc="", ref=[], severity="",
277 resolution=""):
277 resolution="", data=""):
278278
279279 vuln_obj = model.common.factory.createModelObject(
280280 Vuln.class_signature,
287287 self.__addPendingAction(Modelactions.ADDVULNHOST, vuln_obj)
288288 return vuln_obj.getID()
289289
290 def createAndAddVulnToService(self, host_id, service_id, name, desc="", data="",
291 ref=[], severity="", resolution=""):
290 def createAndAddVulnToService(self, host_id, service_id, name, desc="",
291 ref=[], severity="", resolution="", data=""):
292292
293293 vuln_obj = model.common.factory.createModelObject(
294294 Vuln.class_signature,
301301 self.__addPendingAction(Modelactions.ADDVULNSRV, vuln_obj)
302302 return vuln_obj.getID()
303303
304 def createAndAddVulnWebToService(self, host_id, service_id, name, data="", desc="",
304 def createAndAddVulnWebToService(self, host_id, service_id, name, desc="",
305305 ref=[], severity="", resolution="",
306306 website="", path="", request="",
307307 response="", method="", pname="",
308 params="", query="", category=""):
308 params="", query="", category="", data=""):
309309 vulnweb_obj = model.common.factory.createModelObject(
310310 VulnWeb.class_signature,
311311 name, data=data, desc=desc, refs=ref, severity=severity,
77 '''
88
99 import pprint
10 import socket
1011 from plugins import core
1112 from lxml import objectify
1213 from urlparse import urlparse
2021 __status__ = "Development"
2122
2223
23 def get_ip(domain):
24 try:
25 data = socket.gethostbyname_ex(domain)
26 ip = repr(data[2])
27 return ip
28 except Exception:
29 return domain
30
3124
3225 def cleaner_unicode(string):
3326 if string is not None:
4336 self.obj_xml = objectify.fromstring(output)
4437
4538 def parse_issues(self):
46 for issue in self.obj_xml["issue-type-group"]["item"]:
47 url_list = []
39 issue_type = self.parse_issue_type()
40 for issue in self.obj_xml["issue-group"]["item"]:
41 issue_data = issue_type[issue['issue-type']['ref']]
4842 obj_issue = {}
4943
50 obj_issue["name"] = issue["name"].text
51 obj_issue['advisory'] = issue["advisory"]["ref"].text
52
53 if("cve" in issue):
54 obj_issue['cve'] = issue["cve"].text
55
56 for threat in self.obj_xml["url-group"]["item"]:
57 if threat["issue-type"] == issue["fix-recommendation"]["ref"]:
58
59 url_list.append(threat['name'].text)
60
61 obj_issue['urls'] = url_list
62
63 for item in self.obj_xml["issue-group"]["item"]:
64
65 if int(item["url"]["ref"]) == int(threat.get('id')):
66 if "test-http-traffic" in item["variant-group"]["item"] and item["issue-type"]["ref"] == threat['issue-type']:
67
68 http_traffic = item["variant-group"]["item"]["test-http-traffic"].text.split("\n\n")
69
70 obj_issue["request"] = http_traffic[0]
71 obj_issue["response"] = http_traffic[1]
72
73 if(issue["threat-class"]["ref"] == item["threat-class"]["ref"]):
74
75 obj_issue["severity"] = item["severity"].text
76 obj_issue["cvss_score"] = item["cvss-score"].text
77 if ("issue-tip" in item["variant-group"]["item"]["issue-information"]):
78 obj_issue["issue_description"] = item["variant-group"]["item"]["issue-information"]["issue-tip"].text
79 break
44 obj_issue["name"] = issue_data["name"]
45 obj_issue['advisory'] = issue_data["advisory"]
46
47 if("cve" in issue_data):
48 obj_issue['cve'] = issue_data["cve"].text
49
50 obj_issue['url'] = self.get_url(issue['url']['ref'].text)
51 obj_issue['cvss_score'] = issue["cvss-score"].text
52 obj_issue['response'] = self.get_response(issue)
53 obj_issue['request'] = issue['variant-group']['item']["test-http-traffic"].text
54 obj_issue['method'] = self.get_method(issue['variant-group']['item']["test-http-traffic"].text)
55 obj_issue['severity'] = issue['severity'].text
56 obj_issue['issue-description'] = self.parse_advisory_group(issue_data['advisory'])
8057
8158 for recomendation in self.obj_xml["fix-recommendation-group"]["item"]:
8259 full_data = ""
83 if(recomendation.attrib['id'] == issue["fix-recommendation"]["ref"]):
60 if(recomendation.attrib['id'] == issue_data["fix-recommendation"]):
8461 for data in recomendation['general']['fixRecommendation']["text"]:
8562 full_data += '' + data
8663 obj_issue["recomendation"] = full_data
8764 if(hasattr(recomendation['general']['fixRecommendation'], 'link')):
8865 obj_issue["ref_link"] = recomendation['general']['fixRecommendation']['link'].text
89
66
9067 self.issue_list.append(obj_issue)
91
9268 return self.issue_list
69
70 def parse_hosts(self):
71 hosts_list = []
72
73 for host in self.obj_xml['scan-configuration']['scanned-hosts']['item']:
74 hosts_dict = {}
75 hosts_dict['ip'] = socket.gethostbyname(host['host'].text)
76 hosts_dict['hostname'] = host['host'].text
77 hosts_dict['os'] = host['operating-system'].text
78 hosts_dict['port'] = host['port'].text
79
80 if host['port'].text == '443':
81 hosts_dict['scheme'] = 'https'
82 else:
83 hosts_dict['scheme'] = 'http'
84
85 hosts_list.append(hosts_dict)
86
87 return hosts_list
88
89 def parse_issue_type(self):
90 res = {}
91
92 for issue_type in self.obj_xml["issue-type-group"]["item"]:
93 res[issue_type.attrib['id']] = {
94 'name': issue_type.name.text,
95 'advisory': issue_type["advisory"]["ref"].text,
96 'fix-recommendation': issue_type["fix-recommendation"]["ref"].text
97 }
98
99 if "cve" in issue_type:
100 res[issue_type.attrib['id']] = {'cve': issue_type["cve"].text}
101
102 return res
103
104 def parse_advisory_group(self, advisory):
105 '''
106 Function that parse advisory-group in order to get the item's description
107 '''
108 for item in self.obj_xml["advisory-group"]["item"]:
109 if item.attrib['id'] == advisory:
110 return item['advisory']['testTechnicalDescription']['text'].text
111
112 def get_url(self, ref):
113 for item in self.obj_xml['url-group']['item']:
114 if item.attrib['id'] == ref:
115 return item['name'].text
116
117 def get_method(self, http_traffic):
118 methods_list = ['GET','POST','PUT','DELETE','CONNECT','PATCH', 'HEAD', 'OPTIONS']
119
120 try:
121 if http_traffic:
122 for item in methods_list:
123 if http_traffic.startswith(item):
124 return item
125
126 except TypeError:
127 return None
128
129 return None
130
131 def get_response(self, node):
132 try:
133 response = node['variant-group']['item']['issue-information']["testResponseChunk"].text
134 return response
135 except AttributeError:
136 return None
93137
94138 def get_scan_information(self):
95139
116160
117161 parser = AppscanParser(output)
118162 issues = parser.parse_issues()
163 scanned_hosts = parser.parse_hosts()
164 hosts_dict = {}
165
166 for host in scanned_hosts:
167 host_id = self.createAndAddHost(host['ip'], os=host['os'], hostnames=[host['hostname']])
168 service_id = self.createAndAddServiceToHost(
169 host_id,
170 host['scheme'],
171 ports=[host['port']],
172 protocol="tcp?HTTP")
173
174 hosts_dict['://'.join([host['scheme'], host['hostname']])] = {'host_id': host_id, 'service_id': service_id}
175
119176 for issue in issues:
120
121 if "urls" not in issue:
122 continue
123
124 for url in issue["urls"]:
125
126 url_parsed = urlparse(url)
127
128 # Get domain of URL.
129 if url_parsed.netloc:
130 hostname = url_parsed.netloc
131 ip = get_ip(url_parsed.netloc)
132 elif url_parsed.path:
133 hostname = url_parsed.path
134 ip = get_ip(url_parsed.path)
135
136 host_id = self.createAndAddHost(ip)
137 interface_id = self.createAndAddInterface(
138 host_id,
139 ip,
140 ipv4_address=ip,
141 hostname_resolution=[hostname])
142
143 service_id = self.createAndAddServiceToInterface(
144 host_id,
145 interface_id,
146 "HTTP Server",
147 protocol="tcp?HTTP")
148
149 refs = []
150 if "ref_link" in issue:
151 refs.append("Fix link: " + issue["ref_link"])
152 if "cvss_score" in issue:
153 refs.append("CVSS Score: " + issue["cvss_score"])
154 if "cve" in issue:
155 refs.append("CVE: " + issue["cve"])
156 if "advisory" in issue:
157 refs.append("Advisory: " + issue["advisory"])
158
159 self.createAndAddVulnWebToService(
160 host_id,
161 service_id,
162 cleaner_unicode(issue["name"]),
163 cleaner_unicode(issue["issue_description"]) if "issue_description" in issue else "",
164 ref=refs,
165 severity=issue["severity"],
166 resolution=cleaner_unicode(issue["recomendation"]),
167 website=hostname,
168 path=url_parsed.path,
169 request=cleaner_unicode(issue["request"]) if "request" in issue else "",
170 response=cleaner_unicode(issue["response"]) if "response" in issue else "",
171 method=issue["request"][0:3] if "request" in issue else "")
177 url_parsed = urlparse(str(issue['url']))
178 url_string = '://'.join([url_parsed.scheme, url_parsed.netloc])
179 for key in hosts_dict:
180 if url_string == key:
181 h_id = hosts_dict[key]['host_id']
182 s_id = hosts_dict[key]['service_id']
183 refs = []
184 if "ref_link" in issue:
185 refs.append("Fix link: " + issue["ref_link"])
186 if "cvss_score" in issue:
187 refs.append("CVSS Score: " + issue["cvss_score"])
188 if "cve" in issue:
189 refs.append("CVE: " + issue["cve"])
190 if "advisory" in issue:
191 refs.append("Advisory: " + issue["advisory"])
192
193 self.createAndAddVulnWebToService(
194 h_id,
195 s_id,
196 cleaner_unicode(issue["name"]),
197 desc=cleaner_unicode(issue["issue_description"]) if "issue_description" in issue else "",
198 ref=refs,
199 severity=issue["severity"],
200 resolution=cleaner_unicode(issue["recomendation"]),
201 website=url_parsed.netloc,
202 path=url_parsed.path,
203 request=cleaner_unicode(issue["request"]) if "request" in issue else "",
204 response=cleaner_unicode(issue["response"]) if issue["response"] else "",
205 method=issue["method"] if issue["method"] else "")
172206
173207 return
174208
178212
179213 def createPlugin():
180214 return AppscanPlugin()
215
216
217 if __name__ == '__main__':
218 parser = AppscanPlugin()
219 with open('/home/javier/Reports_Testing/appscan-demo_testfire.xml', 'r') as report:
220 parser.parseOutputString(report.read())
221 for item in parser.items:
222 if item.status == 'up':
223 print item
1010 import re
1111 import os
1212 import random
13 import socket
1314 from collections import defaultdict
1415
1516 from plugins import core
162163 elif '# Lynis Report' in output:
163164 lde = LynisLogDataExtracter(output=output)
164165
165 h_id = self.createAndAddHost(name=lde.hostname(),
166 os=lde.osfullname())
166 hostname = lde.hostname()
167 ip = socket.gethostbyname(hostname)
168 h_id = self.createAndAddHost(name=ip, os=lde.osfullname(), hostnames=[hostname])
167169
168170 self.createAndAddVulnToHost(
169171 host_id=h_id,
171173 severity='info',
172174 desc=lde.kernelVersion()
173175 )
176
174177
175178 interfaces = lde.interfaces()
176179 macs = lde.macs()
274274 "nexpose_full_output-%s.xml" % self._rid)
275275
276276 def parseOutputString(self, output, debug=False):
277
277278 parser = NexposeFullXmlParser(output)
278279
279280 for item in parser.items:
281
280282 h_id = self.createAndAddHost(item['name'], item['os'])
281 i_id = self.createAndAddInterface(h_id, item['name'], ipv4_address=item[
282 'name'], hostname_resolution= ' '.join( list( item['hostnames'] )))
283
284 i_id = self.createAndAddInterface(
285 h_id,
286 item['name'],
287 ipv4_address=item['name'],
288 hostname_resolution=' '.join(list(item['hostnames'])))
283289
284290 for v in item['vulns']:
285 v_id = self.createAndAddVulnToHost(h_id, v['name'], v['desc'], v[
286 'refs'], v['severity'], v['resolution'])
291
292 v_id = self.createAndAddVulnToHost(
293 h_id,
294 v['name'],
295 v['desc'],
296 v['refs'],
297 v['severity'],
298 v['resolution'])
287299
288300 for s in item['services']:
289301 web = False
290302 version = s.get("version", "")
291303
292 s_id = self.createAndAddServiceToInterface(h_id, i_id, s['name'],
293 s['protocol'],
294 ports=[
295 str(s['port'])],
296 status=s['status'],
297 version=version)
304 s_id = self.createAndAddServiceToInterface(
305 h_id,
306 i_id,
307 s['name'],
308 s['protocol'],
309 ports=[str(s['port'])],
310 status=s['status'],
311 version=version)
312
298313 for v in s['vulns']:
299314 if v['is_web']:
300315 v_id = self.createAndAddVulnWebToService(
301 h_id, s_id, v['name'], v['desc'], v['refs'],
302 v['severity'], v['resolution'],
316 h_id,
317 s_id,
318 v['name'],
319 v['desc'],
320 v['refs'],
321 v['severity'],
322 v['resolution'],
303323 path=v.get('path',''))
304324 else:
305325 v_id = self.createAndAddVulnToService(
306 h_id, s_id, v['name'], v['desc'], v['refs'],
307 v['severity'], v['resolution'])
326 h_id,
327 s_id,
328 v['name'],
329 v['desc'],
330 v['refs'],
331 v['severity'],
332 v['resolution'])
308333 del parser
309334
310335 def processCommandString(self, username, current_path, command_string):
203203 # value_dict is a dictionary with every detail in the host
204204 for key,value_dict in detail.items():
205205 service_detail = self.get_service_from_details(value_dict,port)
206
206
207207 if service_detail:
208208 return service_detail
209
209
210210 # if the service is not in detail, we will search it in
211211 # the file port_mapper.txt
212212 srv = self.filter_services()
256256 # dict value:
257257 # key is port or protocol of the service
258258 # value is service description
259 res = None
260 priority = 0
259261 for key, value in value_dict.items():
260262 if value == 'Services':
261263 aux_port = port.split('/')[0]
262
263264 key_splited = key.split(',')
264
265265 if key_splited[0] == aux_port:
266 return key_splited[2]
266 res = key_splited[2]
267 priority = 3
267268
268 for k,v in value_dict.items():
269 if '/' in k:
270 auxiliar_key = k.split('/')[0]
271
269 elif '/' in key and priority != 3:
270 auxiliar_key = key.split('/')[0]
272271 if auxiliar_key == port.split('/')[0]:
273 return v
274
275 elif k.isdigit():
276 if k == port.split('/')[0]:
277 return v
278 elif '::' in k:
279 aux_key = k.split('::')[0]
272 res = value
273 priority = 2
274
275 elif key.isdigit() and priority == 0:
276 if key == port.split('/')[0]:
277 res = value
278 priority = 1
279
280 elif '::' in key and priority == 0:
281 aux_key = key.split('::')[0]
280282 auxiliar_port = port.split('/')[0]
281
282283 if aux_key == auxiliar_port:
283 return v
284
285
286 return None
284 res = value
285
286 return res
287287
288288
289289 class OpenvasPlugin(core.PluginBase):
1313 import os
1414 import pprint
1515 import sys
16 import socket
17 from urlparse import urlparse
18
1619
1720 try:
1821 import xml.etree.cElementTree as ET
7780 """
7881 @return items A list of Host instances
7982 """
80 bugtype = ""
81
82 bug_typelist = tree.findall('bugTypeList')[0]
83 for bug_type in bug_typelist.findall('bugType'):
84
85 bugtype = bug_type.get('name')
86
87 bug_list = bug_type.findall('bugList')[0]
88 for item_bug in bug_list.findall('bug'):
89 yield Item(item_bug, bugtype)
90
91
92 """
93 <bugTypeList>
94 <bugType name="File Handling"><bugList><bug level="1"><url>
95 http://www.saludactiva.org.ar/index.php?id=http%3A%2F%2Fwww.google.fr%2F
96 </url><parameter>
97 id=http%3A%2F%2Fwww.google.fr%2F
98 </parameter><info>
99 Warning include() (id)
100 </info>
101 """
83
84 yield Item(tree)
85
10286
10387
10488 def get_attrib_from_subnode(xml_node, subnode_xpath_expr, attrib_name):
145129 @param item_node A item_node taken from an wapiti xml tree
146130 """
147131
148 def __init__(self, item_node, bugtype):
132 def __init__(self, item_node):
149133 self.node = item_node
150
151 self.bugtype = bugtype
152 self.buglevel = self.node.get('level')
153 self.url = self.do_clean(self.get_text_from_subnode('url'))
154 self.parameter = self.do_clean(self.get_text_from_subnode('parameter'))
155 self.info = self.do_clean(self.get_text_from_subnode('info'))
134 self.url = self.get_url(item_node)
135 self.ip = socket.gethostbyname(self.url.hostname)
136 self.hostname = self.url.hostname
137 self.port = self.get_port(self.url)
138 self.scheme = self.url.scheme
139 self.vulns = self.get_vulns(item_node)
156140
157141 def do_clean(self, value):
158142 myreturn = ""
160144 myreturn = re.sub("\n", "", value)
161145 return myreturn
162146
163 def get_text_from_subnode(self, subnode_xpath_expr):
147 def get_text_from_subnode(self, node, subnode_xpath_expr):
164148 """
165149 Finds a subnode in the host node and the retrieves a value from it.
166150
167151 @return An attribute value
168152 """
169 sub_node = self.node.find(subnode_xpath_expr)
153 sub_node = node.find(subnode_xpath_expr)
170154 if sub_node is not None:
171 return sub_node.text
155 return sub_node.text.strip()
172156
173157 return None
158
159 def get_url(self, item_node):
160 target = self.get_info(item_node,'target')
161 return urlparse(target)
162
163 def get_info(self, item_node,name):
164 path = item_node.findall('report_infos/info')
165
166 for item in path:
167 if item.attrib['name'] == name:
168 return item.text
169
170 def get_port(self, url):
171 if url.port:
172 return url.port
173 else:
174 if url.scheme == "http":
175 return "80"
176 elif url.scheme == "https":
177 return "443"
178
179 def get_vulns(self, item_node):
180 vulns_node = item_node.findall('vulnerabilities/vulnerability')
181 vulns_list = []
182
183 for vuln in vulns_node:
184 vulns_dict = {}
185 vulns_dict['id'] = vuln.attrib['name']
186 vulns_dict['description'] = self.get_text_from_subnode(vuln,'description')
187 vulns_dict['solution'] = self.get_text_from_subnode(vuln,'solution')
188 vulns_dict['references'] = self.get_references(vuln)
189 vulns_dict['entries'] = self.get_entries(vuln)
190 vulns_list.append(vulns_dict)
191
192 return vulns_list
193
194 def get_references(self, node):
195 refs = node.findall('references/reference')
196 references_list = []
197 for ref in refs:
198 references_list.append('Title: ' + self.get_text_from_subnode(ref,'title'))
199 references_list.append('URL: ' + self.get_text_from_subnode(ref,'url'))
200
201 return references_list
202
203 def get_entries(self,node):
204 entries = node.findall('entries/entry')
205 entries_list = []
206 for entry in entries:
207 entries_dict = {}
208 entries_dict['method'] = self.get_text_from_subnode(entry,'method')
209 entries_dict['path'] = self.get_text_from_subnode(entry,'path')
210 entries_dict['level'] = self.severity_format(entry)
211 entries_dict['parameter'] = self.get_text_from_subnode(entry,'parameter')
212 entries_dict['http_request'] = self.get_text_from_subnode(entry,'http_request')
213 entries_dict['curl_command'] = self.get_text_from_subnode(entry,'curl_command')
214 entries_list.append(entries_dict)
215
216 return entries_list
217
218 def severity_format(self, node):
219 """
220 Convert Nexpose severity format into Faraday API severity format
221
222 @return a severity
223 """
224 severity = self.get_text_from_subnode(node, 'level')
225
226 if severity == '1':
227 return 'high'
228 elif severity == '2':
229 return 'medium'
230 elif severity == '3':
231 return 'low'
174232
175233
176234 class WapitiPlugin(core.PluginBase):
233291 self._output_file_path = os.path.join(self.data_path,
234292 "wapiti_output-%s.xml" % self._rid)
235293
236 def parseOutputString(self, output, debug=False):
294 def parseOutputString(self, output):
237295 """
238296 This method will discard the output the shell sends, it will read it from
239297 the xml where it expects it to be present.
240
241 NOTE: if 'debug' is true then it is being run from a test case and the
242 output being sent is valid.
243 """
244
245 self._output_file_path = "/root/dev/faraday/trunk/src/report/wapiti2.3.0_abaco.xml"
246 #TODO FOR mig_white_4896... if NOT debug
247 # OR replace all if/else with
248 # parser = WapitiXmlParser(output)
249 if debug:
250 parser = WapitiXmlParser(output)
251 else:
252 if not os.path.exists(self._output_file_path):
253 return False
254
255 parser = WapitiXmlParser(self._output_file_path)
256
257 """
258 self.bugtype=bugtype
259 self.buglevel=self.node.get('level')
260 self.url = self.get_text_from_subnode('url')
261 self.parameter = self.get_text_from_subnode('parameter')
262 self.info = self.get_text_from_subnode('info')
263 """
264
265 h_id = self.createAndAddHost(self.host)
266 i_id = self.createAndAddInterface(
267 h_id, self.host, ipv4_address=self.host)
268 i = 1
298 """
299
300 parser = WapitiXmlParser(output)
269301 for item in parser.items:
270 mport = "%s%i" % (self.port, i)
271 s_id = self.createAndAddServiceToInterface(h_id, i_id, mport,
272 "tcp",
273 [mport],
274 status="(%s) (%s)" % (
275 item.bugtype, item.url),
276 version=item.parameter,
277 description=item.info)
278 i = i + 1
279
280 del parser
281
302 host_id = self.createAndAddHost(item.ip, hostnames=[item.hostname])
303 service_id = self.createAndAddServiceToHost(host_id, item.scheme, protocol='tcp', ports=[item.port])
304 for vuln in item.vulns:
305 for entry in vuln['entries']:
306 vuln_id = self.createAndAddVulnWebToService(host_id,
307 service_id,
308 vuln['id'],
309 desc=vuln['description'],
310 ref=vuln['references'],
311 resolution=vuln['solution'],
312 severity=entry['level'],
313 website=entry['curl_command'],
314 path=entry['path'],
315 request=entry['http_request'],
316 method=entry['method'],
317 params=entry['parameter'])
318
319
320
282321 xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$")
322
323
283324
284325 def processCommandString(self, username, current_path, command_string):
285326 """
310351 return WapitiPlugin()
311352
312353 if __name__ == '__main__':
313 parser = WapitiXmlParser(sys.argv[1])
314 for item in parser.items:
315 if item.status == 'up':
316 print item
354 parser = WapitiPlugin()
355 with open('/home/javier/Reports_Testing/wapiti3.0.1.xml') as report:
356 parser.parseOutputString(report.read())
99 import sys
1010 import argparse
1111 import os
12 from config.globals import CONST_FARADAY_HOME_PATH
13 from server.config import FARADAY_BASE
1214
1315 my_env = os.environ
1416
15 url = my_env["CS_NESSUS_URL"] if 'CS_NESSUS_URL' in my_env else "https://127.0.0.1:8834"
16 username = my_env["CS_NESSUS_USER"] if 'CS_NESSUS_USER' in my_env else "nessus"
17 password = my_env["CS_NESSUS_PASS"] if 'CS_NESSUS_PASS' in my_env else "nessus"
18 profile = my_env["CS_NESSUS_PROFILE"] if 'CS_NESSUS_PROFILE' in my_env else "'Basic Network Scan'"
17 url = my_env["CS_NESSUS_URL"] if 'CS_NESSUS_URL' in my_env else "https://192.168.10.230:8834"
18 username = my_env["CS_NESSUS_USER"] if 'CS_NESSUS_USER' in my_env else "cscan"
19 password = my_env["CS_NESSUS_PASS"] if 'CS_NESSUS_PASS' in my_env else "XqjympHtrvVU22xtK5ZZ"
20 profile = my_env["CS_NESSUS_PROFILE"] if 'CS_NESSUS_PROFILE' in my_env else "Basic Network Scan"
1921
2022 verify = False
2123 token = ''
24
25 import urllib3
26 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
2227
2328
2429 def build_url(resource):
6772 """
6873 Login to nessus.
6974 """
70
7175 login = {'username': usr, 'password': pwd}
7276 data = connect('POST', '/session', data=login)
7377
200204 return data['status'] == 'ready'
201205
202206
203 def export(sid, hid):
207 def export(sid):
204208 """
205209 Make an export request
206210
210214 is made, we have to wait for the export to be ready.
211215 """
212216
213 data = {'history_id': hid,
214 'format': 'nessus'}
217 data = {'format': 'nessus'}
215218
216219 data = connect('POST', '/scans/{0}/export'.format(sid), data=data)
217220
223226 return fid
224227
225228
226 def download(sid, fid, output):
229 def download(sid, fid, output=None):
227230 """
228231 Download the scan results
229232
232235 """
233236
234237 data = connect('GET', '/scans/{0}/export/{1}/download'.format(sid, fid))
235
236 print('Saving scan results to {0}.'.format(output))
237 with open(output, 'w') as f:
238 f.write(data)
238 # For version 7, use the nessus scan Id to avoid overwrite the output file
239 if not output:
240 print('Using Nessus 7. Ignore --output. This is normal.')
241 report_path = os.path.join(FARADAY_BASE,'scripts','cscan','output','nessus_{0}.xml'.format(sid))
242 if not os.path.exists(report_path):
243 with open(report_path,'w') as report:
244 print('Saving scan results to {0}.'.format(report_path))
245 report.write(data)
246
247 else:
248 print('Saving scan results to {0}.'.format(output))
249 with open(output, 'w') as report:
250 report.write(data)
239251
240252
241253 def delete(sid):
258270 """
259271
260272 connect('DELETE', '/scans/{0}/history/{1}'.format(sid, hid))
273
274 def get_scans():
275 scan_list = []
276 data = connect('GET','/scans')
277 for scans in data['scans']:
278 scans_info = {}
279 scans_info['id'] = scans['id']
280 scans_info['creation_date'] = scans['creation_date']
281 scan_list.append(scans_info)
282
283 scan_list = sorted(scan_list,key=lambda scan:scan['creation_date'])
284 return scan_list
285
286 def get_date():
287 with open(os.path.join(CONST_FARADAY_HOME_PATH,'cscan','date.txt'),'r') as date_file:
288 date = date_file.read()
289 try:
290 date = int(date)
291 except ValueError:
292 # Default date: September 3, 2018 20:45 (GMT)
293 return 1536007534
294
295 return date
296
297 def set_date(date):
298 with open(os.path.join(CONST_FARADAY_HOME_PATH,'cscan','date.txt'),'w') as date_file:
299 date_file.write(str(date))
300
301 def get_version():
302 data = connect('GET','/server/properties')
303 return int(data['nessus_ui_version'][0])
304
305
306 def create_directory():
307 if not os.path.exists(os.path.join(CONST_FARADAY_HOME_PATH,'cscan')):
308 os.mkdir(os.path.join(CONST_FARADAY_HOME_PATH,'cscan'))
309 if not os.path.exists(os.path.join(CONST_FARADAY_HOME_PATH,'cscan','date.txt')):
310 open(os.path.join(CONST_FARADAY_HOME_PATH,'cscan','date.txt'),'w').close()
261311
262312 if __name__ == "__main__":
263313 parser = argparse.ArgumentParser(description='nessus_client is develop for automating security testing')
264314 parser.add_argument('-t', '--target', help='Network or Host for scan', required=False)
265315 parser.add_argument('-o', '--output', help='Output file', required=False)
266316 args = parser.parse_args()
267
268317 # Review de Command input
269318 if args.target is None or args.output is None:
270319 print "Argument errors check -h"
277326 print "Unexpected error:", sys.exc_info()[0]
278327 raise
279328
280 print('Adding new scan.' + token)
281 print args.target
282
283 policies = get_policies()
284 policy_id = policies[profile]
285 scan_data = add('CScan nessus', 'Create a new scan with API', args.target, policy_id)
286 scan_id = scan_data['id']
287
288 print('Launching new scan.')
289 scan_uuid = launch(scan_id)
290 history_ids = get_history_ids(scan_id)
291 history_id = history_ids[scan_uuid]
292 while status(scan_id, history_id) not in ('completed', 'canceled'):
293 time.sleep(5)
294
295 print('Exporting the completed scan.')
296 file_id = export(scan_id, history_id)
297 download(scan_id, file_id, args.output)
298
299 print('Deleting the scan.')
300 history_delete(scan_id, history_id)
301 delete(scan_id)
329 version = get_version()
330 if version < 7 :
331 #For Nessus <7
332 print('Adding new scan.' + token)
333 print args.target
334
335 policies = get_policies()
336 policy_id = policies[profile]
337 scan_data = add('CScan nessus', 'Create a new scan with API', args.target, policy_id)
338 scan_id = scan_data['id']
339
340 print('Launching new scan.')
341 scan_uuid = launch(scan_id)
342 history_ids = get_history_ids(scan_id)
343 history_id = history_ids[scan_uuid]
344 while status(scan_id, history_id) not in ('completed', 'canceled'):
345 time.sleep(5)
346
347 print('Exporting the completed scan.')
348 file_id = export(scan_id)
349 download(scan_id, file_id, args.output)
350
351 print('Deleting the scan.')
352 history_delete(scan_id, history_id)
353 delete(scan_id)
354
355 else:
356 #For Nessus >7
357 create_directory()
358 scans = get_scans()
359 date = get_date()
360 for scan in scans:
361 if scan['creation_date'] > date:
362 set_date(scan['creation_date'])
363 print('Downloading scan. Id: {0}'.format(scan['id']))
364 file_id = export(scan['id'])
365 download(scan['id'], file_id)
366 else:
367 print('Scan up to date. Id: {0}'.format(scan['id']))
302368
303369 print('Logout')
304 logout()
370 logout()
0 ###
1 ## Faraday Penetration Test IDE
2 ## Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/)
3 ## See the file 'doc/LICENSE' for the license information
4 ###
5
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 ###
4 ## Faraday Penetration Test IDE
5 ## Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/)
6 ## See the file 'doc/LICENSE' for the license information
7 ###
8
9
10 threshold = 0.75
11 min_weight = 0.3
12
13 rules = [
14 {
15 'id': 'PARENT_TEST',
16 'model': 'Vulnerability',
17 'parent': '192.168.1.18',
18 'object': "regex=^generic-",
19 'conditions': ["severity=info"],
20 'actions': ["--UPDATE:severity=critical"]
21 },
22 {
23 'id': 'CIFRADO_DEBIL',
24 'model': 'Vulnerability',
25 'object': "name=EN-Cifrado%Débil%(SSL%weak%ciphers)",
26 'actions': ["--UPDATE:severity=info"]
27 },
28
29 {
30 'id': 'CLIENT_TEST',
31 'model': 'Vulnerability',
32 'parent': '50.56.220.123',
33 'object': "regex=^Device",
34 'conditions': ["severity=info", "creator=Nessus regex=^OS"],
35 'actions': ["--UPDATE:severity=critical", "--UPDATE:confirmed=True"]
36 },
37
38 {
39 'id': 'CLIENT_TEST_2',
40 'model': 'Vulnerability',
41 'parent': 'http',
42 'object': "regex=Email target=200.58.121.156",
43 'conditions': ["severity=info", "creator=Burp"],
44 'actions': ["--UPDATE:severity=med", "--UPDATE:confirmed=True"]
45 },
46
47 {
48 'id': 'CLIENT_TEST_3',
49 'model': 'Vulnerability',
50 'parent': '320131ea90e3986c8221291c683d6d19bfe8503b',
51 'object': "creator=Nessus --old",
52 'conditions': ["severity=info", "creator=Nessus"],
53 'actions': ["--UPDATE:refs=VCritical", "--UPDATE:confirmed=True"]
54 },
55
56 {
57 'id': 'CU1',
58 'model': 'Vulnerability',
59 'parent': '50.56.220.123',
60 'object': "severity=critical",
61 'actions': ["--UPDATE:severity=info"]
62 },
63
64 {
65 'id': 'CU2',
66 'model': 'Vulnerability',
67 'parent': '50.56.220.123',
68 'object': "severity=info confirmed=True",
69 'actions': ["--EXECUTE:ls"]
70 },
71
72 {
73 'id': 'CU3A',
74 'model': 'Vulnerability',
75 'fields': ['name'],
76 'actions': ["--UPDATE:confirmed=False"]
77 },
78
79 {
80 'id': 'CU3B',
81 'model': 'Vulnerability',
82 'fields': ['name'],
83 'object': "--old",
84 'actions': ["--UPDATE:confirmed=True"]
85 },
86
87 {
88 'id': 'CU4',
89 'model': 'Vulnerability',
90 'object': "name=Email%addresses%disclosed creator=Burp",
91 'actions': ["--UPDATE:refs=RefsX"]
92 },
93
94 {
95 'id': 'CU4B',
96 'model': 'Vulnerability',
97 'object': "name=Email%addresses%disclosed creator=Burp",
98 'actions': ["--UPDATE:-refs=RefsY"]
99 },
100
101 {
102 'id': 'CU5',
103 'model': 'Vulnerability',
104 'object': "name=OS%Identification",
105 'actions': ["--UPDATE:template=445"]
106 },
107
108 {
109 'id': 'CU5B1',
110 'model': 'Vulnerability',
111 'object': "severity=critical",
112 'actions': ["--UPDATE:template=EN-Cifrado Debil (SSL weak ciphers)"]
113 },
114
115 {
116 'id': 'CU5B',
117 'model': 'Vulnerability',
118 'object': "severity=critical",
119 'actions': ["--UPDATE:template=EN-Cifrado Debil (SSL weak ciphers)"]
120 },
121 {
122 'id': 'CU6',
123 'model': 'Service',
124 'object': "name=http",
125 'actions': ["--UPDATE:owned=True"]
126 },
127 {
128 'id': 'CU6B',
129 'model': 'Service',
130 'fields': ['name'],
131 'actions': ["--UPDATE:description=SET BY RULE"]
132 },
133
134 {
135 'id': 'CU7',
136 'model': 'Host',
137 'object': "name=172.16.138.1",
138 'actions': ["--DELETE:"]
139 },
140
141 {
142 'id': 'CU7B',
143 'model': 'Host',
144 'fields': ['name'],
145 'actions': ["--UPDATE:owned=True"]
146 }
147
148 ]
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 ###
4 ## Faraday Penetration Test IDE
5 ## Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/)
6 ## See the file 'doc/LICENSE' for the license information
7 ###
8
9 import argparse
10 import os
11 import signal
12 import smtplib
13 import sqlite3
14 import subprocess
15 import sys
16 from datetime import datetime
17 from difflib import SequenceMatcher
18 from email.mime.multipart import MIMEMultipart
19 from email.mime.text import MIMEText
20 import requests
21 import json
22 from config.configuration import getInstanceConfiguration
23 from persistence.server import models
24 from persistence.server import server
25 from persistence.server.server import login_user
26 from persistence.server.server_io_exceptions import ResourceDoesNotExist, ConflictInDatabase
27 from validator import *
28 import urlparse
29
30
31 logger = logging.getLogger('Faraday searcher')
32
33 reload(sys)
34 sys.setdefaultencoding("utf-8")
35
36 CONF = getInstanceConfiguration()
37
38
39 def send_mail(to_addr, subject, body):
40 from_addr = '[email protected]'
41 msg = MIMEMultipart()
42 msg['From'] = from_addr
43 msg['To'] = to_addr
44 msg['Subject'] = subject
45
46 msg.attach(MIMEText(body, 'plain'))
47 try:
48 server_mail = smtplib.SMTP('smtp.gmail.com', 587)
49 server_mail.starttls()
50 server_mail.login(from_addr, "faradaySearcher.2018")
51 text = msg.as_string()
52 server_mail.sendmail(from_addr, to_addr, text)
53 server_mail.quit()
54 except Exception as error:
55 logger.error("Error: unable to send email")
56 logger.error(error)
57
58
59 def compare(a, b):
60 return SequenceMatcher(None, a, b).ratio()
61
62
63 def get_cwe(data, _server='http://127.0.0.1:5985/'):
64 logger.debug("Getting vulnerability templates from %s " % _server)
65 try:
66 url = urlparse.urljoin(_server, "_api/v2/vulnerability_template/")
67 session_cookie = CONF.getDBSessionCookies()
68 response = requests.request("GET", url, cookies=session_cookie)
69 if response.status_code == 200:
70 templates = json.loads(response.content)
71 cwe = None
72 for row in templates['rows']:
73 doc = row['doc']
74 _id = doc['_id']
75 name = doc['name']
76 description = doc['description']
77 resolution = doc['resolution']
78 if str(_id) == data or name == data:
79 cwe = {
80 'id': _id,
81 'name': name,
82 'description': description,
83 'resolution': resolution
84 }
85 break
86 return cwe
87 elif response.status_code == 401:
88 logger.error('You are not authorized to get the vulnerability templates')
89 return None
90 else:
91 logger.error('We can\'t get the vulnerability templates')
92 return None
93
94 except Exception as error:
95 logger.error(error)
96 return None
97
98
99 def is_same_level(model1, model2):
100 return model1.parent_id == model2.parent_id
101
102
103 def equals(m1, m2, rule):
104 logger.debug("Comparing by similarity '%s' and '%s'" % (m1.name, m2.name))
105 match = True
106 total_ratio = 0
107 count_fields = 0
108
109 for field in rule['fields']:
110 f_m1 = getattr(m1, field, None)
111 f_m2 = getattr(m2, field, None)
112
113 if f_m1 is not None and f_m2 is not None:
114 if field == 'severity' or field == 'owner' or field == 'status':
115 if f_m1 == f_m2:
116 ratio = 1.0
117 else:
118 ratio = min_weight
119
120 elif isinstance(f_m1, str) or isinstance(f_m1, unicode) and isinstance(f_m2, str) or isinstance(f_m2,
121 unicode):
122 ratio = compare(f_m1.lower().replace('\n', ' '), f_m2.lower().replace('\n', ' '))
123
124 elif isinstance(f_m1, bool) and isinstance(f_m2, bool):
125 if f_m1 == f_m2:
126 ratio = 1.0
127 else:
128 ratio = 0.0
129 else:
130 ratio = -1
131
132 if ratio is not -1:
133 total_ratio += ratio
134 count_fields += 1
135
136 if total_ratio != 0:
137 percent = (total_ratio * 100) / count_fields
138 else:
139 percent = 0.0
140 logger.debug("Verify result with %.2f %% evaluating rule %s:" % (percent, rule['id']))
141
142 if match and total_ratio >= (threshold * count_fields):
143 logger.info("MATCH")
144 return True
145 return False
146
147
148 def get_model_environment(model, _models):
149 environment = []
150 for md in _models:
151 if is_same_level(model, md):
152 environment.append(md)
153 return environment
154
155
156 def process_models_by_similarity(ws, _models, rule, _server):
157 logger.debug("--> Start Process models by similarity")
158 for index_m1, m1 in zip(range(len(_models) - 1), _models):
159 for index_m2, m2 in zip(range(index_m1 + 1, len(_models)), _models[index_m1 + 1:]):
160 if m1.id != m2.id and is_same_level(m1, m2):
161 if equals(m1, m2, rule):
162 environment = [m1, m2]
163 _objs_value = None
164 if 'object' in rule:
165 _objs_value = rule['object']
166 _object = get_object(environment, _objs_value)
167 if _object is not None:
168 if 'conditions' in rule:
169 environment = get_model_environment(m2, _models)
170 if can_execute_action(environment, rule['conditions']):
171 execute_action(ws, _object, rule, _server)
172 else:
173 execute_action(ws, _object, rule, _server)
174 logger.debug("<-- Finish Process models by similarity")
175
176
177 def insert_rule(_id, command, obj, selector, fields=None, key=None, value=None, output_file='output/searcher.db'):
178 logger.debug("Inserting rule %s into SQlite database ..." % _id)
179 conn = sqlite3.connect(output_file)
180 conn.text_factory = str
181 try:
182 cursor = conn.cursor()
183 cursor.execute('''CREATE TABLE IF NOT EXISTS rule (
184 id TEXT,
185 model TEXT NOT NULL,
186 fields TEXT,
187 command TEXT NOT NULL,
188 object_id TEXT NOT NULL,
189 object_name TEXT NOT NULL,
190 key TEXT,
191 value TEXT,
192 created TEXT NOT NULL,
193 selector TEXT)''')
194
195 created = str(datetime.now())
196 rule = (_id, obj.class_signature, fields, command, obj.id, obj.name, key, value, created, selector)
197 cursor.execute('INSERT INTO rule VALUES (?,?,?,?,?,?,?,?,?,?)', rule)
198 conn.commit()
199 conn.close()
200 logger.debug("Done")
201 except sqlite3.Error as e:
202 conn.close()
203 logger.error(e)
204
205
206 def get_field(obj, field):
207 try:
208 if field in obj.__dict__:
209 return getattr(obj, field)
210 return None
211 except AttributeError:
212 logger.error("ERROR: Field %s is invalid" % field)
213 return None
214
215
216 def set_array(field, value, add=True):
217 if isinstance(field, list):
218 if add:
219 if value not in field:
220 field.append(value)
221 else:
222 if value in field:
223 field.remove(value)
224
225
226 def update_vulnerability(ws, vuln, key, value, _server):
227 if key == 'template':
228 cwe = get_cwe(value, _server)
229 if cwe is None:
230 logger.error("%s: cwe not found" % value)
231 return False
232
233 vuln.name = cwe['name']
234 vuln.description = cwe['description']
235 vuln.desc = cwe['description']
236 vuln.resolution = cwe['resolution']
237
238 logger.info("Applying template '%s' to vulnerability '%s' with id '%s'" % (value, vuln.name, vuln.id))
239
240 elif key == 'confirmed':
241 value = value == 'True'
242 vuln.confirmed = value
243 logger.info("Changing property %s to %s in vulnerability '%s' with id %s" % (key, value, vuln.name, vuln.id))
244 elif key == 'owned':
245 value = value == 'True'
246 vuln.owned = value
247 logger.info("Changing property %s to %s in vulnerability '%s' with id %s" % (key, value, vuln.name, vuln.id))
248 else:
249 to_add = True
250 if key.startswith('-'):
251 key = key.strip('-')
252 to_add = False
253
254 field = get_field(vuln, key)
255 if field is not None:
256 if isinstance(field, str) or isinstance(field, unicode):
257 setattr(vuln, key, value)
258 logger.info(
259 "Changing property %s to %s in vulnerability '%s' with id %s" % (key, value, vuln.name, vuln.id))
260 else:
261 set_array(field, value, add=to_add)
262 action = 'Adding %s to %s list in vulnerability %s with id %s' % (value, key, vuln.name, vuln.id)
263 if not to_add:
264 action = 'Removing %s from %s list in vulnerability %s with id %s' % (
265 value, key, vuln.name, vuln.id)
266
267 logger.info(action)
268
269 try:
270 if vuln.class_signature == "Vulnerability":
271 models.update_vuln(ws, vuln)
272
273 elif vuln.class_signature == "VulnerabilityWeb":
274 models.update_vuln_web(ws, vuln)
275
276 except ConflictInDatabase:
277 logger.error("There was a conflict trying to save '%s' with ID: %s" % (vuln.name, vuln.id))
278 return False
279 except Exception as error:
280 logger.error(error)
281 return False
282
283 logger.info("Done")
284 return True
285
286
287 def update_service(ws, service, key, value):
288 if key == 'owned':
289 value = value == 'True'
290 service.owned = value
291 logger.info("Changing property %s to %s in service '%s' with id %s" % (key, value, service.name, service.id))
292 else:
293 to_add = True
294 if key.startswith('-'):
295 key = key.strip('-')
296 to_add = False
297
298 field = get_field(service, key)
299 if field is not None:
300 if isinstance(field, str) or isinstance(field, unicode):
301 setattr(service, key, value)
302 logger.info(
303 "Changing property %s to %s in service '%s' with id %s" % (key, value, service.name, service.id))
304 else:
305 set_array(field, value, add=to_add)
306 action = 'Adding %s to %s list in service %s with id %s' % (value, key, service.name, service.id)
307 if not to_add:
308 action = 'Removing %s from %s list in service %s with id %s' % (
309 value, key, service.name, service.id)
310
311 logger.info(action)
312 try:
313 models.update_service(ws, service, "")
314 except Exception as error:
315 logger.error(error)
316 return False
317
318 logger.info("Done")
319 return True
320
321
322 def update_host(ws, host, key, value):
323 if key == 'owned':
324 value = value == 'True'
325 host.owned = value
326 logger.info("Changing property %s to %s in host '%s' with id %s" % (key, value, host.name, host.id))
327 else:
328 to_add = True
329 if key.startswith('-'):
330 key = key.strip('-')
331 to_add = False
332
333 field = get_field(host, key)
334 if field is not None:
335 if isinstance(field, str) or isinstance(field, unicode):
336 setattr(host, key, value)
337 logger.info("Changing property %s to %s in host '%s' with id %s" % (key, value, host.name, host.id))
338 else:
339 set_array(field, value, add=to_add)
340 action = 'Adding %s to %s list in host %s with id %s' % (value, key, host.name, host.id)
341 if not to_add:
342 action = 'Removing %s from %s list in host %s with id %s' % (
343 value, key, host.name, host.id)
344
345 logger.info(action)
346 try:
347 models.update_host(ws, host, "")
348 except Exception as error:
349 logger.error(error)
350 return False
351
352 logger.info("Done")
353 return True
354
355
356 def get_parent(ws, parent_tag):
357 logger.debug("Getting parent")
358 try:
359 parent = models.get_host(ws, parent_tag) or models.get_service(ws, parent_tag)
360 except ResourceDoesNotExist:
361 parent = models.get_hosts(ws, name=parent_tag) or models.get_services(ws, name=parent_tag)
362 if len(parent) == 0:
363 return None
364
365 return parent
366
367
368 def filter_objects_by_parent(_objects, parent):
369 objects = []
370 parents = []
371 if isinstance(parent, list):
372 parents.extend(parent)
373 else:
374 parents.append(parent)
375 for obj in _objects:
376 for p in parents:
377 if p.id == obj.parent_id:
378 objects.append(obj)
379 return objects
380
381
382 def evaluate_condition(model, condition):
383 key, value = condition.split('=')
384 value = value.replace('%', ' ')
385 if key == 'regex':
386 if re.match(value, model.name) is None:
387 return False
388 return True
389
390 temp_value = getattr(model, key, None)
391 if key in model.getMetadata():
392 temp_value = model.getMetadata()[key]
393
394 if temp_value is None:
395 return False
396
397 if isinstance(temp_value, list):
398 if value not in temp_value and str(value) not in temp_value and int(value) not in temp_value:
399 return False
400 return True
401
402 if isinstance(temp_value, bool):
403 if value == 'True' and not temp_value:
404 return False
405 if value == 'False' and temp_value:
406 return False
407 return True
408
409 if value.encode("utf-8") != temp_value.encode("utf-8"):
410 return False
411 return True
412
413
414 def get_object(_models, obj):
415 logger.debug("Getting object")
416 objects = []
417 if obj is None:
418 if len(_models) > 0:
419 objects.append(_models[-1])
420 return objects
421 return None
422
423 items = obj.split()
424 allow_old_option = '--old' in items
425 if allow_old_option:
426 items.remove('--old')
427 for model in _models:
428 if all([evaluate_condition(model, cond) for cond in items]):
429 objects.append(model)
430 if allow_old_option:
431 break
432 return objects
433
434
435 def get_models(ws, objects, rule):
436 logger.debug("Getting models")
437 if 'parent' in rule:
438 parent = get_parent(ws, rule['parent'])
439 if parent is None:
440 logger.warning("WARNING: Parent %s not found in rule %s " % (rule['parent'], rule['id']))
441 return objects
442 return filter_objects_by_parent(objects, parent)
443 return objects
444
445
446 def evaluate_conditions(_models, conditions):
447 logger.debug("Evaluating conditions")
448 for model in _models:
449 if all([evaluate_condition(model, cond) for cond in conditions]):
450 return True
451 return False
452
453
454 def can_execute_action(_models, conditions):
455 for conds in conditions:
456 conds = conds.split()
457 if not evaluate_conditions(_models, conds):
458 return False
459 return True
460
461
462 def execute_action(ws, objects, rule, _server):
463 logger.info("Running actions of rule '%s' :" % rule['id'])
464 actions = rule['actions']
465 _objs_value = None
466 if 'object' in rule:
467 _objs_value = rule['object']
468
469 for obj in objects:
470 for action in actions:
471 action = action.strip('--')
472 command, expression = action.split(':')
473
474 if command == 'UPDATE':
475 key, value = expression.split('=')
476 if obj.class_signature == 'VulnerabilityWeb' or obj.class_signature == 'Vulnerability':
477 if update_vulnerability(ws, obj, key, value, _server):
478 insert_rule(rule['id'], command, obj, _objs_value, fields=None, key=key, value=value)
479
480 if obj.class_signature == 'Service':
481 update_service(ws, obj, key, value)
482
483 if obj.class_signature == 'Host':
484 update_host(ws, obj, key, value)
485
486 elif command == 'DELETE':
487 if obj.class_signature == 'VulnerabilityWeb':
488 models.delete_vuln_web(ws, obj.id)
489 logger.info(" Deleting vulnerability web '%s' with id '%s':" % (obj.name, obj.id))
490 insert_rule(rule['id'], command, obj, _objs_value)
491
492 elif obj.class_signature == 'Vulnerability':
493 models.delete_vuln(ws, obj.id)
494 logger.info("Deleting vulnerability '%s' with id '%s':" % (obj.name, obj.id))
495
496 elif obj.class_signature == 'Service':
497 models.delete_service(ws, obj.id)
498 logger.info("Deleting service '%s' with id '%s':" % (obj.name, obj.id))
499
500 elif obj.class_signature == 'Host':
501 models.delete_host(ws, obj.id)
502 logger.info("Deleting host '%s' with id '%s':" % (obj.name, obj.id))
503
504 elif command == 'EXECUTE':
505 if subprocess.call(expression, shell=True, stdin=None) is 0:
506 logger.info("Running command: '%s'" % expression)
507 insert_rule(rule['id'], command, obj, _objs_value, fields=None, key=None, value=expression)
508 else:
509 logger.error("Operation fail running command: '%s'" % expression)
510 return False
511 else:
512 subject = 'Faraday searcher alert'
513 body = '%s %s have been modified by rule %s at %s' % (
514 obj.class_signature, obj.name, rule['id'], str(datetime.now()))
515 send_mail(expression, subject, body)
516 insert_rule(rule['id'], command, obj, _objs_value, fields=None, key=None, value=expression)
517 logger.info("Sending mail to: '%s'" % expression)
518 return True
519
520
521 def process_vulnerabilities(ws, vulns, _server):
522 logger.debug("--> Start Process vulnerabilities")
523 for rule in rules:
524 if rule['model'] == 'Vulnerability':
525 vulnerabilities = get_models(ws, vulns, rule)
526 if 'fields' in rule:
527 process_models_by_similarity(ws, vulnerabilities, rule, _server)
528 else:
529 _objs_value = None
530 if 'object' in rule:
531 _objs_value = rule['object']
532 objects = get_object(vulnerabilities, _objs_value)
533 if objects is not None and len(objects) != 0:
534 if 'conditions' in rule:
535 if can_execute_action(vulnerabilities, rule['conditions']):
536 execute_action(ws, objects, rule, _server)
537 else:
538 execute_action(ws, objects, rule, _server)
539 logger.debug("<-- Finish Process vulnerabilities")
540
541
542 def process_services(ws, services, _server):
543 logger.debug("--> Start Process services")
544 for rule in rules:
545 if rule['model'] == 'Service':
546 services = get_models(ws, services, rule)
547 if 'fields' in rule:
548 process_models_by_similarity(ws, services, rule, _server)
549 pass
550 else:
551 _objs_value = None
552 if 'object' in rule:
553 _objs_value = rule['object']
554 objects = get_object(services, _objs_value)
555 if objects is not None and len(objects) != 0:
556 if 'conditions' in rule:
557 if can_execute_action(services, rule['conditions']):
558 execute_action(ws, objects, rule, _server)
559 else:
560 execute_action(ws, objects, rule, _server)
561 logger.debug("<-- Finish Process services")
562
563
564 def process_hosts(ws, hosts, _server):
565 logger.debug("--> Start Process Hosts")
566 for rule in rules:
567 if rule['model'] == 'Host':
568 hosts = get_models(ws, hosts, rule)
569 if 'fields' in rule:
570 process_models_by_similarity(ws, hosts, rule, _server)
571 pass
572 else:
573 _objs_value = None
574 if 'object' in rule:
575 _objs_value = rule['object']
576 objects = get_object(hosts, _objs_value)
577 if objects is not None and len(objects) != 0:
578 if 'conditions' in rule:
579 if can_execute_action(hosts, rule['conditions']):
580 execute_action(ws, objects, rule, _server)
581 else:
582 execute_action(ws, objects, rule, _server)
583 logger.debug("<-- Finish Process Hosts")
584
585
586 def lock_file(lockfile):
587 if os.path.isfile(lockfile):
588 return False
589 else:
590 f = open(lockfile, 'w')
591 f.close()
592 return True
593
594
595 def signal_handler(signal, frame):
596 os.remove(".lock.pod")
597 logger.info('Killed')
598 sys.exit(0)
599
600
601 def main():
602 signal.signal(signal.SIGINT, signal_handler)
603
604 parser = argparse.ArgumentParser(description='Search duplicated objects on Faraday')
605 parser.add_argument('-w', '--workspace', help='Search duplicated objects into this workspace', required=True)
606 parser.add_argument('-s', '--server', help='Faraday server', required=False, default="http://127.0.0.1:5985/")
607 parser.add_argument('-u', '--user', help='Faraday user', required=False, default="")
608 parser.add_argument('-p', '--password', help='Faraday password', required=False, default="")
609 parser.add_argument('-o', '--output', help='Choose a custom output directory', required=False)
610 parser.add_argument('-l', '--log', help='Choose a custom log level', required=False)
611 args = parser.parse_args()
612
613 lockf = ".lock.pod"
614 if not lock_file(lockf):
615 print ("You can run only one instance of searcher (%s)" % lockf)
616 exit(0)
617
618 workspace = ''
619 if args.workspace:
620 workspace = args.workspace
621 else:
622 print("You must enter a workspace in command line, please use --help to read more")
623 os.remove(lockf)
624 exit(0)
625
626 _server = 'http://127.0.0.1:5985/'
627 if args.server:
628 _server = args.server
629
630 _user = 'faraday'
631 if args.user:
632 _user = args.user
633
634 _password = 'changeme'
635 if args.password:
636 _password = args.password
637
638 output = 'output/'
639 if args.output:
640 output = args.output
641
642 loglevel = 'debug'
643 if args.log:
644 loglevel = args.log
645
646 for d in [output, 'log/']:
647 if not os.path.isdir(d):
648 os.makedirs(d)
649
650 numeric_level = getattr(logging, loglevel.upper(), None)
651 if not isinstance(numeric_level, int):
652 raise ValueError('Invalid log level: %s' % loglevel)
653
654 if not logger.handlers:
655 logger.propagate = 0
656 logger.setLevel(numeric_level)
657 fh = logging.FileHandler('log/searcher.log')
658 fh.setLevel(numeric_level)
659 # create console handler with a higher log level
660 ch = logging.StreamHandler()
661 ch.setLevel(numeric_level)
662 # create formatter and add it to the handlers
663 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
664
665 fh.setFormatter(formatter)
666 ch.setFormatter(formatter)
667
668 logger.addHandler(fh)
669 logger.addHandler(ch)
670
671 try:
672 session_cookie = login_user(_server, _user, _password)
673 if not session_cookie:
674 raise UserWarning('Invalid credentials!')
675 else:
676 CONF.setDBUser(_user)
677 CONF.setDBSessionCookies(session_cookie)
678
679 server.AUTH_USER = _user
680 server.AUTH_PASS = _password
681 server.SERVER_URL = _server
682 server.FARADAY_UP = False
683
684 logger.info('Started')
685 logger.info('Searching objects into workspace %s ' % workspace)
686
687 logger.debug("Getting hosts ...")
688 hosts = models.get_hosts(workspace)
689
690 logger.debug("Getting services ...")
691 services = models.get_services(workspace)
692
693 logger.debug("Getting vulnerabilities ...")
694 vulns = models.get_all_vulns(workspace)
695
696 if validate_rules():
697 process_vulnerabilities(workspace, vulns, _server)
698 process_services(workspace, services, _server)
699 process_hosts(workspace, hosts, _server)
700
701 # Remove lockfile
702 os.remove(lockf)
703
704 logger.info('Finished')
705
706 except ResourceDoesNotExist:
707 logger.error("Resource not found")
708 os.remove(lockf)
709 exit(0)
710
711 except Exception as errorMsg:
712 logger.error(errorMsg)
713 os.remove(lockf)
714 exit(0)
715
716
717 if __name__ == "__main__":
718 main()
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 ###
4 ## Faraday Penetration Test IDE
5 ## Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/)
6 ## See the file 'doc/LICENSE' for the license information
7 ###
8
9 import re
10 import logging
11 from rules import *
12
13 logger = logging.getLogger('Faraday searcher')
14
15 vfields = {
16 'Vulnerability': ['name', 'desc', 'description', 'severity', 'data', 'confirmed', 'owned', 'owner', 'resolution',
17 'status'],
18
19 'Service': ['name', 'description', 'owned', 'owner', 'parent', 'ports', 'protocol', 'status', 'version'],
20
21 'Host': ['name', 'default_gateway', 'description', 'ipv4', 'ipv6', 'os', 'owned', 'owner']
22 }
23
24 commands = ['UPDATE', 'DELETE', 'ALERT', 'EXECUTE']
25
26
27 def validate_id(id_list, rule_id):
28 return rule_id not in id_list
29
30
31 def validate_model(model):
32 if model is not 'Host' and model is not 'Service' and model is not 'Vulnerability':
33 return False
34 return True
35
36
37 def validate_parent(parent):
38 return parent != ''
39
40
41 def validate_fields(model, fields):
42 if model in vfields and len(fields) != 0:
43 for field in fields:
44 if field not in vfields[model]:
45 print "ERROR: The field '%s' doesn't exist in model '%s'" % (field, model)
46 logger.error("The field '%s' doesn't exist in model '%s'" % (field, model))
47 return False
48 return True
49 else:
50 return False
51
52
53 def validate_indexer(indexer, allow_old_option=False):
54 array = indexer.split()
55 for item in array:
56 array = item.split('=')
57 if allow_old_option:
58 if item != '--old' and len(array) != 2 or '' in array:
59 logger.error("ERROR: '%s' must have 'field=value' or '--old'" % item)
60 return False
61
62 elif len(array) != 2 or '' in array:
63 logger.error("ERROR: '%s' must have 'field=value' " % item)
64 return False
65
66 return True
67
68
69 def validate_object(obj):
70 if obj == '':
71 return False
72 return validate_indexer(obj, allow_old_option=True)
73
74
75 def validate_conditions(conditions):
76 if len(conditions) == 0:
77 return False
78
79 for cond in conditions:
80 if not validate_indexer(cond):
81 return False
82 return True
83
84
85 def validate_action(actions):
86 if len(actions) == 0:
87 return False
88
89 for action in actions:
90 if not action.startswith('--UPDATE:') and not action.startswith('--ALERT:') and not action.startswith(
91 '--EXECUTE:') and not action.startswith('--DELETE:'):
92 return False
93
94 if action.startswith('--UPDATE:'):
95 expression = action.strip('--UPDATE:')
96 if len(expression.split('=')) != 2 or expression.split('=')[0] == '' or expression.split('=')[1] == '':
97 return False
98
99 if action.startswith('--ALERT:'):
100 expression = action.strip('--ALERT:')
101 if expression == '' or re.match("^(.+\@.+\..+)$", expression) is None:
102 return False
103
104 if action.startswith('--EXECUTE:'):
105 expression = action.strip('--EXECUTE:')
106 if expression == '':
107 return False
108
109 if action.startswith('--DELETE:'):
110 expression = action.strip('--DELETE:')
111 if expression != '':
112 return False
113
114 return True
115
116
117 def validate(key, dictionary, validate_function=None, rule_id=None, mandatory=True, **args):
118 if rule_id is None:
119 if key not in dictionary:
120 logger.error("ERROR: Key %s doesn't exist" % key)
121 return False
122 if not validate_function(args['id_list'], dictionary[key]):
123 logger.error("ERROR: Key %s is repeated" % key)
124 return False
125 else:
126 if key not in dictionary and mandatory:
127 logger.error("ERROR: Key %s doesn't exist in rule: %s" % (key, rule_id))
128 return False
129 if key in dictionary:
130 if key == 'fields':
131 if not validate_function(args['model'], dictionary[key]):
132 logger.error("ERROR: Key %s has an invalid value in rule: %s" % (key, rule_id))
133 return False
134 return True
135
136 if not validate_function(dictionary[key]):
137 logger.error("ERROR: Key %s has an invalid value in rule: %s" % (key, rule_id))
138 return False
139
140 return True
141
142
143 def validate_rules():
144 logger.info('--> Validating rules ...')
145 id_list = []
146 for rule in rules:
147 if not validate('id', rule, validate_id, id_list=id_list):
148 return False
149 rule_id = rule['id']
150 id_list.append(rule_id)
151
152 if not validate('model', rule, validate_model, rule_id):
153 return False
154 model = rule['model']
155
156 if not validate('parent', rule, validate_parent, rule_id, mandatory=False):
157 return False
158
159 if not validate('fields', rule, validate_fields, rule_id, mandatory=False, model=model):
160 return False
161
162 if not validate('object', rule, validate_object, rule_id, mandatory=False):
163 return False
164
165 if not validate('conditions', rule, validate_conditions, rule_id, mandatory=False):
166 return False
167
168 if not validate('actions', rule, validate_action, rule_id):
169 return False
170
171 logger.info('<-- Rules OK')
172 return True
0 # Faraday Penetration Test IDE
1 # Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/)
2 # See the file 'doc/LICENSE' for the license information
3 import datetime
4 import time
5 from datetime import datetime
6
7 from flask import Blueprint
8 from marshmallow import fields
9
10 from server.api.base import AutoSchema, ReadWriteWorkspacedView, PaginatedMixin
11 from server.models import Command
12 from server.schemas import PrimaryKeyRelatedField
13
14 activityfeed_api = Blueprint('activityfeed_api', __name__)
15
16
17 class ActivityFeedSchema(AutoSchema):
18 _id = fields.Integer(dump_only=True, attribute='id')
19 itime = fields.Method(serialize='get_itime', deserialize='load_itime', required=True, attribute='start_date')
20 sum_created_vulnerabilities = fields.Method(serialize='get_sum_created_vulnerabilities', allow_none=True)
21 sum_created_hosts = fields.Method(serialize='get_sum_created_hosts', allow_none=True)
22 sum_created_services = fields.Method(serialize='get_sum_created_services', allow_none=True)
23 sum_created_vulnerability_critical = fields.Method(serialize='get_sum_created_vulnerability_critical',
24 allow_none=True)
25 workspace = PrimaryKeyRelatedField('name', dump_only=True)
26
27 def load_itime(self, value):
28 return datetime.datetime.fromtimestamp(value)
29
30 def get_itime(self, obj):
31 return time.mktime(obj.start_date.utctimetuple()) * 1000
32
33 def get_sum_created_vulnerabilities(self, obj):
34 return obj.sum_created_vulnerabilities
35
36 def get_sum_created_hosts(self, obj):
37 return obj.sum_created_hosts
38
39 def get_sum_created_services(self, obj):
40 return obj.sum_created_services
41
42 def get_sum_created_vulnerability_critical(self, obj):
43 return obj.sum_created_vulnerability_critical
44
45 class Meta:
46 model = Command
47 fields = ('_id', 'command', 'ip', 'hostname',
48 'params', 'user', 'workspace', 'tool',
49 'import_source', 'itime', 'sum_created_vulnerabilities',
50 'sum_created_hosts', 'sum_created_services', 'sum_created_vulnerability_critical')
51
52
53 class ActivityFeedView(PaginatedMixin, ReadWriteWorkspacedView):
54 route_base = 'activities'
55 model_class = Command
56 schema_class = ActivityFeedSchema
57 get_joinedloads = [Command.workspace]
58 order_field = Command.start_date.desc()
59
60 def _envelope_list(self, objects, pagination_metadata=None):
61 commands = []
62 for command in objects:
63 commands.append({
64 '_id': command['_id'],
65 'user': command['user'],
66 'import_source': command['import_source'],
67 'command': command['command'],
68 'tool': command['tool'],
69 'params': command['params'],
70 'vulnerabilities_count': (command['sum_created_vulnerabilities'] or 0),
71 'hosts_count': command['sum_created_hosts'] or 0,
72 'services_count': command['sum_created_services'] or 0,
73 'criticalIssue': command['sum_created_vulnerability_critical'] or 0,
74 'date': command['itime'],
75 })
76 return {
77 'activities': commands,
78 }
79
80
81 ActivityFeedView.register(activityfeed_api)
44 import flask
55 import requests
66
7 from server.app import app
7 from flask import Blueprint, abort, make_response, jsonify
88 from server.utils.logger import get_logger
99 from server.utils.web import gzipped
1010
11 exploits_api = Blueprint('exploits_api', __name__)
12
13
1114 @gzipped
12 @app.route('/vulners/exploits/<cveid>', methods=['GET'])
15 @exploits_api.route('/v2/vulners/exploits/<cveid>', methods=['GET'])
1316 def get_exploits(cveid):
1417 """
1518 Use Vulns API to get all exploits available for a specific CVE-ID
3942 if response.status_code == 200:
4043
4144 obj_response = response.json()
45
4246 if obj_response["data"]["references"] is not {}:
4347
44 metasploit_modules = obj_response["data"]["references"][cveid]["metasploit"]
45 exploitdb_modules = obj_response["data"]["references"][cveid]["exploitdb"]
48 metasploit_modules = obj_response["data"]["references"][cveid].get("metasploit", [])
49 exploitdb_modules = obj_response["data"]["references"][cveid].get("exploitdb", [])
4650
4751 for module in metasploit_modules:
4852 obj_module = {}
5862 obj_module.update({"href": module["href"]})
5963 json_response["exploitdb"].append(obj_module)
6064
61 except:
62 return flask.jsonify({})
65 except KeyError as ex:
66 abort(make_response(jsonify(message='Could not find {0}'.format(ex.message)), 400))
6367
6468 return flask.jsonify(json_response)
129129 res_dict = {'hosts':{}}
130130
131131 host_count_schema = HostCountSchema()
132 host_count = Host.query_with_count(False, host_id_list, workspace_name)
132 host_count = Host.query_with_count(None, host_id_list, workspace_name)
133133
134134 for host in host_count.all():
135135 res_dict["hosts"][host.id] = host_count_schema.dump(host).data
125125 required=True)
126126 obj_id = fields.String(dump_only=True, attribute='id')
127127 target = fields.String(dump_only=True, attribute='target_host_ip')
128 host_os = fields.String(dump_only=True, attribute='target_host_os')
128129 metadata = SelfNestedField(CustomMetadataSchema())
129130 date = fields.DateTime(attribute='create_date',
130131 dump_only=True) # This is only used for sorting
140141 'desc', 'impact', 'confirmed', 'name',
141142 'service', 'obj_id', 'type', 'policyviolations',
142143 '_attachments',
143 'target', 'resolution', 'metadata')
144 'target', 'host_os', 'resolution', 'metadata')
144145
145146 def get_type(self, obj):
146147 return obj.__class__.__name__
256257 'desc', 'impact', 'confirmed', 'name',
257258 'service', 'obj_id', 'type', 'policyviolations',
258259 'request', '_attachments', 'params',
259 'target', 'resolution', 'method', 'metadata', 'status_code')
260 'target', 'host_os', 'resolution', 'method', 'metadata',
261 'status_code')
260262
261263
262264 # Use this override for filterset fields that filter by en exact match by
486488 undefer(VulnerabilityGeneric.creator_command_id),
487489 undefer(VulnerabilityGeneric.creator_command_tool),
488490 undefer(VulnerabilityGeneric.target_host_ip),
491 undefer(VulnerabilityGeneric.target_host_os),
489492 joinedload(VulnerabilityGeneric.evidence),
490493 joinedload(VulnerabilityGeneric.tags),
491494 ]
569572 if file_obj:
570573 depot = DepotManager.get()
571574 depot_file = depot.get(file_obj.content.get('file_id'))
575 if depot_file.content_type.startswith('image/'):
576 # Image content types are safe (they can't be executed like
577 # html) so we don't have to force the download of the file
578 as_attachment = False
579 else:
580 as_attachment = True
572581 return flask.send_file(
573582 io.BytesIO(depot_file.read()),
574583 attachment_filename=file_obj.filename,
575 as_attachment=True,
584 as_attachment=as_attachment,
576585 mimetype=depot_file.content_type
577586 )
578587 else:
11 # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/)
22 # See the file 'doc/LICENSE' for the license information
33 import json
4 import re
45
56 import flask
67 from flask import Blueprint
7 from marshmallow import Schema, fields, post_load
8 from marshmallow import Schema, fields, post_load, validate
89 from sqlalchemy.orm import undefer
910
1011 from server.models import db, Workspace
5455
5556
5657 class WorkspaceSchema(AutoSchema):
58
59 name = fields.String(required=True,
60 validate=validate.Regexp(r"^[a-z0-9][a-z0-9\_\$\(\)\+\-\/]*$",0,"ERORROROR"))
5761 stats = SelfNestedField(WorkspaceSummarySchema())
5862 duration = SelfNestedField(WorkspaceDurationSchema())
5963 _id = fields.Integer(dump_only=True, attribute='id')
9498
9599 def _get_base_query(self):
96100 try:
97 only_confirmed = bool(json.loads(flask.request.args['confirmed']))
101 confirmed = bool(json.loads(flask.request.args['confirmed']))
98102 except (KeyError, ValueError):
99 only_confirmed = False
100 return Workspace.query_with_count(only_confirmed)
103 confirmed = None
104 return Workspace.query_with_count(confirmed)
101105
102106 def _perform_create(self, data, **kwargs):
103107 scope = data.pop('scope', [])
6161 def register_blueprints(app):
6262 from server.api.modules.info import info_api
6363 from server.api.modules.commandsrun import commandsrun_api
64 from server.api.modules.activity_feed import activityfeed_api
6465 from server.api.modules.credentials import credentials_api
6566 from server.api.modules.hosts import host_api
6667 from server.api.modules.licenses import license_api
7374 from server.api.modules.comments import comment_api
7475 from server.api.modules.upload_reports import upload_api
7576 from server.api.modules.websocket_auth import websocket_auth_api
77 from server.api.modules.get_exploits import exploits_api
7678 app.register_blueprint(commandsrun_api)
79 app.register_blueprint(activityfeed_api)
7780 app.register_blueprint(credentials_api)
7881 app.register_blueprint(host_api)
7982 app.register_blueprint(info_api)
8790 app.register_blueprint(comment_api)
8891 app.register_blueprint(upload_api)
8992 app.register_blueprint(websocket_auth_api)
93 app.register_blueprint(exploits_api)
9094
9195
9296 def check_testing_configuration(testing, app):
0 from server.web import app
1 from server.models import User, db
2
3 def changes_password(username, password):
4 with app.app_context():
5 user = User.query.filter_by(username=username).first()
6 if user:
7 user.password = password
8 db.session.add(user)
9 db.session.commit()
10 print "Password changed succesfully"
11 else:
12 print "User not found in Faraday's Database"
13
14
99
1010 import os
1111 import sys
12 import click
1213 import psycopg2
1314 from random import SystemRandom
1415 from tempfile import TemporaryFile
6162
6263 return True
6364
64 def run(self):
65 def run(self, choose_password):
6566 """
6667 Main entry point that executes these steps:
6768 * creates role in database.
100101 self._create_tables(conn_string)
101102 couchdb_config_present = server.config.couchdb
102103 if not (couchdb_config_present and couchdb_config_present.user and couchdb_config_present.password):
103 self._create_admin_user(conn_string)
104 self._create_admin_user(conn_string, choose_password)
104105 else:
105106 print('Skipping new admin creation since couchdb configuration was found.')
106107 except KeyboardInterrupt:
108109 print('User cancelled.')
109110 sys.exit(1)
110111
111 def _create_admin_user(self, conn_string):
112 def _create_admin_user(self, conn_string, choose_password):
112113 engine = create_engine(conn_string)
113 random_password = self.generate_random_pw(12)
114 # TODO change the random_password variable name, it is not always
115 # random anymore
116 if choose_password:
117 random_password = click.prompt(
118 'Enter the desired password for the "faraday" user',
119 confirmation_prompt=True,
120 hide_input=True
121 )
122 else:
123 random_password = self.generate_random_pw(12)
114124 already_created = False
115125 try:
116126 engine.execute("INSERT INTO \"faraday_user\" (username, name, password, "
4545 raise
4646
4747
48
4948 def copy_default_config_to_local():
50
5149 if os.path.exists(LOCAL_CONFIG_FILE):
5250 return
5351
7270 __parser = ConfigParser.SafeConfigParser()
7371 __parser.read(CONFIG_FILES)
7472
75 class ConfigSection(object):
76 def __init__(self, name, parser):
77 self.__name = name
78 self.__parser = parser
79
80 def __getattr__(self, option_name):
81 return self.__parser.get(self.__name, option_name)
82
83 for section in __parser.sections():
84 globals()[section] = ConfigSection(section, __parser)
85
73 for section_name in __parser.sections():
74 ConfigSection.parse_section(section_name, __parser._sections[section_name])
8675
8776 def __get_version():
8877 try:
115104 return LOGGING_LEVEL is DEBUG
116105
117106
107 class ConfigSection(object):
108 def parse(self, __parser):
109 for att in self.__dict__:
110 self.__setattr__(att,__parser.get(att))
111
112 @staticmethod
113 def parse_section(section_name, __parser):
114 section = None
115 if section_name == 'couchdb':
116 section = couchdb
117 elif section_name == 'database':
118 section = database
119 elif section_name == 'faraday_server':
120 section = faraday_server
121 elif section_name == 'ldap':
122 section = ldap
123 elif section_name == 'ssl':
124 section = ssl
125 elif section_name == 'storage':
126 section = storage
127 section.parse(__parser)
128
129
130 class CouchDBConfigObject(ConfigSection):
131 def __init__(self):
132 self.host = None
133 self.password = None
134 self.port = None
135 self.protocol = None
136 self.ssl_port = None
137 self.user = None
138
139
140 class DatabaseConfigObject(ConfigSection):
141 def __init__(self):
142 self.connection_string = None
143
144
145 class FaradayServerConfigObject(ConfigSection):
146 def __init__(self):
147 self.bind_address = None
148 self.port = None
149 self.secret_key = None
150 self.websocket_port = None
151
152
153 class LDAPConfigObject(ConfigSection):
154 def __init__(self):
155 self.admin_group = None
156 self.client_group = None
157 self.disconnect_timeout = None
158 self.domain_dn = None
159 self.enabled = None
160 self.pentester_group = None
161 self.port = None
162 self.server = None
163 self.use_ldaps = None
164 self.use_start_tls = None
165
166
167 class SSLConfigObject(ConfigSection):
168 def __init__(self):
169 self.certificate = None
170 self.keyfile = None
171 self.port = None
172
173
174 class StorageConfigObject(ConfigSection):
175 def __init__(self):
176 self.path = None
177
178
179
180 couchdb = CouchDBConfigObject()
181 database = DatabaseConfigObject()
182 faraday_server = FaradayServerConfigObject()
183 ldap = LDAPConfigObject()
184 ssl = SSLConfigObject()
185 storage = StorageConfigObject()
186
118187 parse_and_bind_configuration()
3737 max_size = 1024
3838 thumbnail_format = 'PNG'
3939 thumbnail_size = (128, 128)
40 forbidden_content_types = [
41 'text/html',
42 'application/javascript',
43 ]
4440
4541 def process_content(self, content, filename=None, content_type=None):
46 """Standard implementation of :meth:`.DepotFileInfo.process_content`
47
48 This is the standard depot implementation of files upload, it will
49 store the file on the default depot and will provide the standard
50 attributes.
51
52 Subclasses will need to call this method to ensure the standard
53 set of attributes is provided.
54 """
55
56 file_path, file_id = self.store_content(content, filename, content_type)
57
58 self['file_id'] = file_id
59
60 saved_file = self.file
61 self['content_type'] = saved_file.content_type
62 if any(map(lambda forbidden_content_type: self['content_type'] in forbidden_content_type, self.forbidden_content_types)):
63 raise UserWarning('Content not allowed')
64 self['filename'] = saved_file.filename
65
66 self['path'] = file_path
67 self['uploaded_at'] = saved_file.last_modified.strftime('%Y-%m-%d %H:%M:%S')
68 self['_public_url'] = saved_file.public_url
69
7042 image_format = imghdr.what(None, h=content[:32])
7143 if image_format:
72 self['content_type'] = 'image/{0}'.format(image_format)
44 content_type = 'image/{0}'.format(image_format)
7345 self.generate_thumbnail(content)
46 return super(FaradayUploadedFile, self).process_content(
47 content, filename, content_type)
7448
7549 def generate_thumbnail(self, content):
7650 content = file_from_content(content)
266266 vulnerability_total_count = query_expression()
267267
268268 @classmethod
269 def query_with_count(cls, only_confirmed, host_ids, workspace_name):
269 def query_with_count(cls, confirmed, host_ids, workspace_name):
270270 query = cls.query.join(Workspace).filter(Workspace.name == workspace_name)
271271 if host_ids:
272272 query = query.filter(cls.id.in_(host_ids))
275275 cls.vulnerability_info_count,
276276 _make_vuln_count_property(
277277 type_=None,
278 only_confirmed = only_confirmed,
278 confirmed = confirmed,
279279 use_column_property = False,
280280 extra_query = "vulnerability.severity='informational'",
281281 get_hosts_vulns = True
285285 cls.vulnerability_med_count,
286286 _make_vuln_count_property(
287287 type_ = None,
288 only_confirmed = only_confirmed,
288 confirmed = confirmed,
289289 use_column_property = False,
290290 extra_query = "vulnerability.severity='medium'",
291291 get_hosts_vulns = True
295295 cls.vulnerability_high_count,
296296 _make_vuln_count_property(
297297 type_ = None,
298 only_confirmed = only_confirmed,
298 confirmed = confirmed,
299299 use_column_property = False,
300300 extra_query = "vulnerability.severity='high'",
301301 get_hosts_vulns = True
305305 cls.vulnerability_critical_count,
306306 _make_vuln_count_property(
307307 type_ = None,
308 only_confirmed = only_confirmed,
308 confirmed = confirmed,
309309 use_column_property = False,
310310 extra_query = "vulnerability.severity='critical'",
311311 get_hosts_vulns = True
315315 cls.vulnerability_low_count,
316316 _make_vuln_count_property(
317317 type_ = None,
318 only_confirmed = only_confirmed,
318 confirmed = confirmed,
319319 use_column_property = False,
320320 extra_query = "vulnerability.severity='low'",
321321 get_hosts_vulns = True
325325 cls.vulnerability_unclassified_count,
326326 _make_vuln_count_property(
327327 type_ = None,
328 only_confirmed = only_confirmed,
328 confirmed = confirmed,
329329 use_column_property = False,
330330 extra_query = "vulnerability.severity='unclassified'",
331331 get_hosts_vulns = True
335335 cls.vulnerability_total_count,
336336 _make_vuln_count_property(
337337 type_ = None,
338 only_confirmed = only_confirmed,
338 confirmed = confirmed,
339339 use_column_property = False,
340340 get_hosts_vulns = True
341341 )
880880 deferred=True
881881 )
882882
883 _host_vuln_query = (
883 _host_ip_query = (
884884 select([Host.ip])
885885 .where(text('vulnerability.host_id = host.id'))
886886 )
887 _service_vuln_query = (
887 _service_ip_query = (
888888 select([text('host_inner.ip')])
889889 .select_from(text('host as host_inner, service'))
890890 .where(text('vulnerability.service_id = service.id and '
893893 target_host_ip = column_property(
894894 case([
895895 (text('vulnerability.host_id IS NOT null'),
896 _host_vuln_query.as_scalar()),
896 _host_ip_query.as_scalar()),
897897 (text('vulnerability.service_id IS NOT null'),
898 _service_vuln_query.as_scalar())
898 _service_ip_query.as_scalar())
899 ]),
900 deferred=True
901 )
902
903 _host_os_query = (
904 select([Host.os])
905 .where(text('vulnerability.host_id = host.id'))
906 )
907 _service_os_query = (
908 select([text('host_inner.os')])
909 .select_from(text('host as host_inner, service'))
910 .where(text('vulnerability.service_id = service.id and '
911 'host_inner.id = service.host_id'))
912 )
913 target_host_os = column_property(
914 case([
915 (text('vulnerability.host_id IS NOT null'),
916 _host_os_query.as_scalar()),
917 (text('vulnerability.service_id IS NOT null'),
918 _service_os_query.as_scalar())
899919 ]),
900920 deferred=True
901921 )
12151235 return self.host or self.service
12161236
12171237
1218 def _make_vuln_count_property(type_=None, only_confirmed=False,
1238 def _make_vuln_count_property(type_=None, confirmed=None,
12191239 use_column_property=True, extra_query=None, get_hosts_vulns=False):
12201240 from_clause = table('vulnerability')
12211241
12381258 # This can cause SQL injection vulnerabilities
12391259 # In this case type_ is supplied from a whitelist so this is safe
12401260 query = query.where(text("vulnerability.type = '%s'" % type_))
1241 if only_confirmed:
1261 if confirmed:
12421262 if db.session.bind.dialect.name == 'sqlite':
12431263 # SQLite has no "true" expression, we have to use the integer 1
12441264 # instead
12471267 # I suppose that we're using PostgreSQL, that can't compare
12481268 # booleans with integers
12491269 query = query.where(text("vulnerability.confirmed = true"))
1270 elif confirmed == False:
1271 if db.session.bind.dialect.name == 'sqlite':
1272 # SQLite has no "true" expression, we have to use the integer 1
1273 # instead
1274 query = query.where(text("vulnerability.confirmed = 0"))
1275 elif db.session.bind.dialect.name == 'postgresql':
1276 # I suppose that we're using PostgreSQL, that can't compare
1277 # booleans with integers
1278 query = query.where(text("vulnerability.confirmed = false"))
12501279
12511280 if extra_query:
12521281 query = query.where(text(extra_query))
12831312 cascade="all, delete-orphan")
12841313
12851314 @classmethod
1286 def query_with_count(cls, only_confirmed):
1315 def query_with_count(cls, confirmed):
12871316 """
12881317 Add count fields to the query.
12891318
1290 If only_confirmed is True, it will only show the count for confirmed
1319 If confirmed is True/False, it will only show the count for confirmed / not confirmed
12911320 vulnerabilities. Otherwise, it will show the count of all of them
12921321 """
12931322 return cls.query.options(
12981327 with_expression(
12991328 cls.vulnerability_web_count,
13001329 _make_vuln_count_property('vulnerability_web',
1301 only_confirmed=only_confirmed,
1330 confirmed=confirmed,
13021331 use_column_property=False)
13031332 ),
13041333 with_expression(
13051334 cls.vulnerability_code_count,
13061335 _make_vuln_count_property('vulnerability_code',
1307 only_confirmed=only_confirmed,
1336 confirmed=confirmed,
13081337 use_column_property=False)
13091338 ),
13101339 with_expression(
13111340 cls.vulnerability_standard_count,
13121341 _make_vuln_count_property('vulnerability',
1313 only_confirmed=only_confirmed,
1342 confirmed=confirmed,
13141343 use_column_property=False)
13151344 ),
13161345 with_expression(
13171346 cls.vulnerability_total_count,
13181347 _make_vuln_count_property(type_=None,
1319 only_confirmed=only_confirmed,
1348 confirmed=confirmed,
13201349 use_column_property=False)
13211350 ),
13221351 )
130130 }
131131
132132 .fg-light-gray {
133 color: #a1a1a1;
133 color: #d2d2d2;
134134 }
135135
136136 .seccion.dashboard article header h2{
220220 .btn {
221221 border-radius: 0px;
222222 }
223
224 .feed-icon{
225 width: 18px;
226 padding-bottom: 5px;
227 margin-right: 3px;
228 }
229
230
231 .little-gray{
232 color: #a2a1a1;
233 }
234
235 .hide-border-top{
236 border-top: none!important;
237 }
238
239 .margin-right-15px{
240 margin-right: 15px;
241 }
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
4 <style type="text/css">
5 .st0{fill:#757575;}
6 </style>
7 <title>03</title>
8 <g>
9 <path class="st0" d="M88.9,6.3H11.1c-2.6,0-4.7,2.1-4.7,4.7l0,0v77.8c0,2.6,2.1,4.7,4.7,4.7h77.8c2.6,0,4.7-2.1,4.7-4.7V11.1
10 C93.7,8.5,91.5,6.3,88.9,6.3L88.9,6.3z M26.3,66.8l12.5-12.6L26.3,41.8L33,35l15.8,15.9c1.9,1.8,1.9,4.8,0.1,6.7c0,0,0,0-0.1,0.1
11 L32.9,73.5L26.3,66.8z M67.1,73.6H48.5v-9.5H67L67.1,73.6z"/>
12 </g>
13 </svg>
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
4 <style type="text/css">
5 .st0{fill:#4F4F4F;}
6 </style>
7 <g>
8 <path class="st0" d="M50.5,4C25.4,4,5,24.4,5,49.5S25.4,95,50.5,95S96,74.6,96,49.5S75.6,4,50.5,4z M50.5,91.1
9 c-23,0-41.6-18.7-41.6-41.6c0-23,18.7-41.6,41.6-41.6c23,0,41.6,18.7,41.6,41.6C92.1,72.5,73.5,91.1,50.5,91.1z"/>
10 <path class="st0" d="M74.7,27.1c-0.7-0.8-2-0.8-2.7-0.1L50.7,47.2L29.4,27c-0.8-0.7-2-0.7-2.7,0.1c-0.7,0.8-0.7,2,0.1,2.7l21.1,20
11 l-21.1,20c-0.8,0.7-0.8,2-0.1,2.7c0.4,0.4,0.9,0.6,1.4,0.6c0.5,0,1-0.2,1.3-0.5l21.3-20.2L72,72.7c0.4,0.4,0.9,0.5,1.3,0.5
12 c0.5,0,1-0.2,1.4-0.6c0.7-0.8,0.7-2-0.1-2.7l-21.1-20l21.1-20C75.4,29.1,75.5,27.8,74.7,27.1z"/>
13 </g>
14 </svg>
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
4 <style type="text/css">
5 .st0{fill:#4F4F4F;}
6 </style>
7 <path class="st0" d="M93.7,26.4H32c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h61.7c0.7,0,1.3,0.6,1.3,1.3
8 C95,25.9,94.4,26.4,93.7,26.4z M11.4,83C7.9,83,5,80.1,5,76.6c0-3.6,2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4C17.9,80.1,15,83,11.4,83
9 z M11.4,57.3c-3.5,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4S15,57.3,11.4,57.3z M11.4,31.6c-3.5,0-6.4-2.9-6.4-6.4
10 c0-3.6,2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4C17.9,28.7,15,31.6,11.4,31.6z M32,49.6h47.6c0.7,0,1.3,0.6,1.3,1.3s-0.6,1.3-1.3,1.3
11 H32c-0.7,0-1.3-0.6-1.3-1.3S31.3,49.6,32,49.6z M32,75.3h33.4c0.7,0,1.3,0.6,1.3,1.3c0,0.7-0.6,1.3-1.3,1.3H32
12 c-0.7,0-1.3-0.6-1.3-1.3C30.7,75.9,31.3,75.3,32,75.3z"/>
13 </svg>
00 /*!
1 * ui-grid - v3.0.7 - 2015-10-06
2 * Copyright (c) 2015 ; License: MIT
1 * ui-grid - v4.6.3 - 2018-08-04
2 * Copyright (c) 2018 ; License: MIT
33 */
4
5 (function() {
6 'use strict';
7
8 angular.module('ui.grid.i18n', []);
9 angular.module('ui.grid', ['ui.grid.i18n']);
10 })();
411
512 (function () {
613 'use strict';
7 angular.module('ui.grid.i18n', []);
8 angular.module('ui.grid', ['ui.grid.i18n']);
9 })();
10 (function () {
11 'use strict';
14
15 /**
16 * @ngdoc object
17 * @name ui.grid.service:uiGridConstants
18 * @description Constants for use across many grid features
19 *
20 */
21
22
1223 angular.module('ui.grid').constant('uiGridConstants', {
1324 LOG_DEBUG_MESSAGES: true,
1425 LOG_WARN_MESSAGES: true,
2435 APOS_REGEXP: /'/g,
2536 BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
2637 COL_CLASS_PREFIX: 'ui-grid-col',
38 ENTITY_BINDING: '$$this',
2739 events: {
2840 GRID_SCROLL: 'uiGridScroll',
2941 COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
7183 F11: 122,
7284 F12: 123
7385 },
86 /**
87 * @ngdoc object
88 * @name ASC
89 * @propertyOf ui.grid.service:uiGridConstants
90 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
91 * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
92 * to configure the sorting direction of the column
93 */
7494 ASC: 'asc',
95 /**
96 * @ngdoc object
97 * @name DESC
98 * @propertyOf ui.grid.service:uiGridConstants
99 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
100 * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
101 * to configure the sorting direction of the column
102 */
75103 DESC: 'desc',
104
105
106 /**
107 * @ngdoc object
108 * @name filter
109 * @propertyOf ui.grid.service:uiGridConstants
110 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_filter columnDef.filter}
111 * to configure filtering on the column
112 *
113 * `SELECT` and `INPUT` are used with the `type` property of the filter, the rest are used to specify
114 * one of the built-in conditions.
115 *
116 * Available `condition` options are:
117 * - `uiGridConstants.filter.STARTS_WITH`
118 * - `uiGridConstants.filter.ENDS_WITH`
119 * - `uiGridConstants.filter.CONTAINS`
120 * - `uiGridConstants.filter.GREATER_THAN`
121 * - `uiGridConstants.filter.GREATER_THAN_OR_EQUAL`
122 * - `uiGridConstants.filter.LESS_THAN`
123 * - `uiGridConstants.filter.LESS_THAN_OR_EQUAL`
124 * - `uiGridConstants.filter.NOT_EQUAL`
125 *
126 *
127 * Available `type` options are:
128 * - `uiGridConstants.filter.SELECT` - use a dropdown box for the cell header filter field
129 * - `uiGridConstants.filter.INPUT` - use a text box for the cell header filter field
130 */
76131 filter: {
77132 STARTS_WITH: 2,
78133 ENDS_WITH: 4,
87142 INPUT: 'input'
88143 },
89144
145 /**
146 * @ngdoc object
147 * @name aggregationTypes
148 * @propertyOf ui.grid.service:uiGridConstants
149 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_aggregationType columnDef.aggregationType}
150 * to specify the type of built-in aggregation the column should use.
151 *
152 * Available options are:
153 * - `uiGridConstants.aggregationTypes.sum` - add the values in this column to produce the aggregated value
154 * - `uiGridConstants.aggregationTypes.count` - count the number of rows to produce the aggregated value
155 * - `uiGridConstants.aggregationTypes.avg` - average the values in this column to produce the aggregated value
156 * - `uiGridConstants.aggregationTypes.min` - use the minimum value in this column as the aggregated value
157 * - `uiGridConstants.aggregationTypes.max` - use the maximum value in this column as the aggregated value
158 */
90159 aggregationTypes: {
91160 sum: 2,
92161 count: 4,
95164 max: 32
96165 },
97166
98 // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
99 CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'],
100
167 /**
168 * @ngdoc array
169 * @name CURRENCY_SYMBOLS
170 * @propertyOf ui.grid.service:uiGridConstants
171 * @description A list of all presently circulating currency symbols that was copied from
172 * https://en.wikipedia.org/wiki/Currency_symbol#List_of_presently-circulating_currency_symbols
173 *
174 * Can be used on {@link ui.grid.class:rowSorter} to create a number string regex that ignores currency symbols.
175 */
176 CURRENCY_SYMBOLS: ['¤', '؋', 'Ar', 'Ƀ', '฿', 'B/.', 'Br', 'Bs.', 'Bs.F.', 'GH₵', '¢', 'c', 'Ch.', '₡', 'C$', 'D', 'ден',
177 'دج', '.د.ب', 'د.ع', 'JD', 'د.ك', 'ل.د', 'дин', 'د.ت', 'د.م.', 'د.إ', 'Db', '$', '₫', 'Esc', '€', 'ƒ', 'Ft', 'FBu',
178 'FCFA', 'CFA', 'Fr', 'FRw', 'G', 'gr', '₲', 'h', '₴', '₭', 'Kč', 'kr', 'kn', 'MK', 'ZK', 'Kz', 'K', 'L', 'Le', 'лв',
179 'E', 'lp', 'M', 'KM', 'MT', '₥', 'Nfk', '₦', 'Nu.', 'UM', 'T$', 'MOP$', '₱', 'Pt.', '£', 'ج.م.', 'LL', 'LS', 'P', 'Q',
180 'q', 'R', 'R$', 'ر.ع.', 'ر.ق', 'ر.س', '៛', 'RM', 'p', 'Rf.', '₹', '₨', 'SRe', 'Rp', '₪', 'Ksh', 'Sh.So.', 'USh', 'S/',
181 'SDR', 'сом', '৳ ', 'WS$', '₮', 'VT', '₩', '¥', 'zł'],
182
183 /**
184 * @ngdoc object
185 * @name scrollDirection
186 * @propertyOf ui.grid.service:uiGridConstants
187 * @description Set on {@link ui.grid.class:Grid#properties_scrollDirection Grid.scrollDirection},
188 * to indicate the direction the grid is currently scrolling in
189 *
190 * Available options are:
191 * - `uiGridConstants.scrollDirection.UP` - set when the grid is scrolling up
192 * - `uiGridConstants.scrollDirection.DOWN` - set when the grid is scrolling down
193 * - `uiGridConstants.scrollDirection.LEFT` - set when the grid is scrolling left
194 * - `uiGridConstants.scrollDirection.RIGHT` - set when the grid is scrolling right
195 * - `uiGridConstants.scrollDirection.NONE` - set when the grid is not scrolling, this is the default
196 */
101197 scrollDirection: {
102198 UP: 'up',
103199 DOWN: 'down',
107203
108204 },
109205
206 /**
207 * @ngdoc object
208 * @name dataChange
209 * @propertyOf ui.grid.service:uiGridConstants
210 * @description Used with {@link ui.grid.api:PublicApi#methods_notifyDataChange PublicApi.notifyDataChange},
211 * {@link ui.grid.class:Grid#methods_callDataChangeCallbacks Grid.callDataChangeCallbacks},
212 * and {@link ui.grid.class:Grid#methods_registerDataChangeCallback Grid.registerDataChangeCallback}
213 * to specify the type of the event(s).
214 *
215 * Available options are:
216 * - `uiGridConstants.dataChange.ALL` - listeners fired on any of these events, fires listeners on all events.
217 * - `uiGridConstants.dataChange.EDIT` - fired when the data in a cell is edited
218 * - `uiGridConstants.dataChange.ROW` - fired when a row is added or removed
219 * - `uiGridConstants.dataChange.COLUMN` - fired when the column definitions are modified
220 * - `uiGridConstants.dataChange.OPTIONS` - fired when the grid options are modified
221 */
110222 dataChange: {
111223 ALL: 'all',
112224 EDIT: 'edit',
114226 COLUMN: 'column',
115227 OPTIONS: 'options'
116228 },
229
230 /**
231 * @ngdoc object
232 * @name scrollbars
233 * @propertyOf ui.grid.service:uiGridConstants
234 * @description Used with {@link ui.grid.class:GridOptions#properties_enableHorizontalScrollbar GridOptions.enableHorizontalScrollbar}
235 * and {@link ui.grid.class:GridOptions#properties_enableVerticalScrollbar GridOptions.enableVerticalScrollbar}
236 * to specify the scrollbar policy for that direction.
237 *
238 * Available options are:
239 * - `uiGridConstants.scrollbars.NEVER` - never show scrollbars in this direction
240 * - `uiGridConstants.scrollbars.ALWAYS` - always show scrollbars in this direction
241 * - `uiGridConstants.scrollbars.WHEN_NEEDED` - shows scrollbars in this direction when needed
242 */
243
117244 scrollbars: {
118245 NEVER: 0,
119 ALWAYS: 1
120 //WHEN_NEEDED: 2
246 ALWAYS: 1,
247 WHEN_NEEDED: 2
121248 }
122249 });
123250
124251 })();
252
125253 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
126 var uiGridCell = {
254 return {
127255 priority: 0,
128256 scope: false,
129257 require: '?^uiGrid',
142270 if (uiGridCtrl && $scope.col.compiledElementFn) {
143271 compileTemplate();
144272 }
273
145274 // No controller, compile the element manually (for unit tests)
146275 else {
147 if ( uiGridCtrl && !$scope.col.compiledElementFn ){
148 // gridUtil.logError('Render has been called before precompile. Please log a ui-grid issue');
149
276 if ( uiGridCtrl && !$scope.col.compiledElementFn ) {
150277 $scope.col.getCompiledElementFn()
151278 .then(function (compiledElementFn) {
152279 compiledElementFn($scope, function(clonedElement, scope) {
153280 $elm.append(clonedElement);
154281 });
155 });
282 }).catch(angular.noop);
156283 }
157284 else {
158285 var html = $scope.col.cellTemplate
164291 }
165292 }
166293 },
167 post: function($scope, $elm, $attrs, uiGridCtrl) {
168 var initColClass = $scope.col.getColClass(false);
294 post: function($scope, $elm) {
295 var initColClass = $scope.col.getColClass(false),
296 classAdded;
297
169298 $elm.addClass(initColClass);
170299
171 var classAdded;
172 var updateClass = function( grid ){
300 function updateClass( grid ) {
173301 var contents = $elm;
174 if ( classAdded ){
302
303 if ( classAdded ) {
175304 contents.removeClass( classAdded );
176305 classAdded = null;
177306 }
183312 classAdded = $scope.col.cellClass;
184313 }
185314 contents.addClass(classAdded);
186 };
315 }
187316
188317 if ($scope.col.cellClass) {
189318 updateClass();
190319 }
191
320
192321 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
193322 var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
194
323
195324 // watch the col and row to see if they change - which would indicate that we've scrolled or sorted or otherwise
196325 // changed the row/col that this cell relates to, and we need to re-evaluate cell classes and maybe other things
197 var cellChangeFunction = function( n, o ){
326 function cellChangeFunction( n, o ) {
198327 if ( n !== o ) {
199 if ( classAdded || $scope.col.cellClass ){
328 if ( classAdded || $scope.col.cellClass ) {
200329 updateClass();
201330 }
202331
203332 // See if the column's internal class has changed
204333 var newColClass = $scope.col.getColClass(false);
334
205335 if (newColClass !== initColClass) {
206336 $elm.removeClass(initColClass);
207337 $elm.addClass(newColClass);
208338 initColClass = newColClass;
209339 }
210340 }
211 };
341 }
212342
213343 // TODO(c0bra): Turn this into a deep array watch
214 /* shouldn't be needed any more given track by col.name
215 var colWatchDereg = $scope.$watch( 'col', cellChangeFunction );
216 */
217344 var rowWatchDereg = $scope.$watch( 'row', cellChangeFunction );
218
219
220 var deregisterFunction = function() {
345
346 function deregisterFunction() {
221347 dataChangeDereg();
222 // colWatchDereg();
223 rowWatchDereg();
224 };
225
226 $scope.$on( '$destroy', deregisterFunction );
227 $elm.on( '$destroy', deregisterFunction );
348 rowWatchDereg();
349 }
350
351 $scope.$on('$destroy', deregisterFunction);
352 $elm.on('$destroy', deregisterFunction);
228353 }
229354 };
230355 }
231356 };
232
233 return uiGridCell;
234357 }]);
235358
236
237 (function(){
359 (function() {
238360
239361 angular.module('ui.grid')
240362 .service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
259381 * we're on
260382 *
261383 */
262 initialize: function( $scope, uiGridCtrl ){
384 initialize: function( $scope, uiGridCtrl ) {
263385 $scope.grid = uiGridCtrl.grid;
264386
265387 // Store a reference to this link/controller in the main uiGrid controller
279401 * menuItems based on this. $scope.col needs to be set by the column
280402 * before calling the menu.
281403 * @param {$scope} $scope the $scope from the uiGridColumnMenu
282 * @param {controller} uiGridCtrl the uiGridController for the grid
283 * we're on
284 *
285404 */
286 setColMenuItemWatch: function ( $scope ){
287 var deregFunction = $scope.$watch('col.menuItems', function (n, o) {
405 setColMenuItemWatch: function ( $scope ) {
406 var deregFunction = $scope.$watch('col.menuItems', function (n) {
288407 if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
289408 n.forEach(function (item) {
290409 if (typeof(item.context) === 'undefined' || !item.context) {
320439 *
321440 */
322441 sortable: function( $scope ) {
323 if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
324 return true;
325 }
326 else {
327 return false;
328 }
442 return Boolean( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting);
329443 },
330444
331445 /**
338452 * @param {string} direction the direction that we'd have selected for us to be active
339453 *
340454 */
341 isActiveSort: function( $scope, direction ){
342 return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
455 isActiveSort: function( $scope, direction ) {
456 return Boolean(typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
343457 typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
344
345458 },
346459
347460 /**
353466 *
354467 */
355468 suppressRemoveSort: function( $scope ) {
356 if ($scope.col && $scope.col.suppressRemoveSort) {
357 return true;
358 }
359 else {
360 return false;
361 }
469 return Boolean($scope.col && $scope.col.suppressRemoveSort);
362470 },
363471
364472
378486 *
379487 */
380488 hideable: function( $scope ) {
381 if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
382 return false;
383 }
384 else {
385 return true;
386 }
489 return !(typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false );
387490 },
388491
389492
395498 * @param {$scope} $scope the $scope from the uiGridColumnMenu
396499 *
397500 */
398 getDefaultMenuItems: function( $scope ){
501 getDefaultMenuItems: function( $scope ) {
399502 return [
400503 {
401 title: i18nService.getSafeText('sort.ascending'),
504 title: function() {return i18nService.getSafeText('sort.ascending');},
402505 icon: 'ui-grid-icon-sort-alt-up',
403506 action: function($event) {
404507 $event.stopPropagation();
412515 }
413516 },
414517 {
415 title: i18nService.getSafeText('sort.descending'),
518 title: function() {return i18nService.getSafeText('sort.descending');},
416519 icon: 'ui-grid-icon-sort-alt-down',
417520 action: function($event) {
418521 $event.stopPropagation();
426529 }
427530 },
428531 {
429 title: i18nService.getSafeText('sort.remove'),
532 title: function() {return i18nService.getSafeText('sort.remove');},
430533 icon: 'ui-grid-icon-cancel',
431534 action: function ($event) {
432535 $event.stopPropagation();
440543 }
441544 },
442545 {
443 title: i18nService.getSafeText('column.hide'),
546 title: function() {return i18nService.getSafeText('column.hide');},
444547 icon: 'ui-grid-icon-cancel',
445548 shown: function() {
446549 return service.hideable( $scope );
449552 $event.stopPropagation();
450553 $scope.hideColumn();
451554 }
452 },
453 {
454 title: i18nService.getSafeText('columnMenu.close'),
455 screenReaderOnly: true,
456 shown: function(){
457 return true;
458 },
459 action: function($event){
460 $event.stopPropagation();
461 }
462555 }
463556 ];
464557 },
471564 * @description gets the position information needed to place the column
472565 * menu below the column header
473566 * @param {$scope} $scope the $scope from the uiGridColumnMenu
474 * @param {GridCol} column the column we want to position below
567 * @param {GridColumn} column the column we want to position below
475568 * @param {element} $columnElement the column element we want to position below
476569 * @returns {hash} containing left, top, offset, height, width
477570 *
478571 */
479 getColumnElementPosition: function( $scope, column, $columnElement ){
572 getColumnElementPosition: function( $scope, column, $columnElement ) {
480573 var positionData = {};
574
481575 positionData.left = $columnElement[0].offsetLeft;
482576 positionData.top = $columnElement[0].offsetTop;
483577 positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
503597 * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
504598 * later to fix it
505599 * @param {$scope} $scope the $scope from the uiGridColumnMenu
506 * @param {GridCol} column the column we want to position below
600 * @param {GridColumn} column the column we want to position below
507601 * @param {hash} positionData a hash containing left, top, offset, height, width
508602 * @param {element} $elm the column menu element that we want to reposition
509603 * @param {element} $columnElement the column element that we want to reposition underneath
511605 */
512606 repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
513607 var menu = $elm[0].querySelectorAll('.ui-grid-menu');
514 var containerId = column.renderContainer ? column.renderContainer : 'body';
515 var renderContainer = column.grid.renderContainers[containerId];
516608
517609 // It's possible that the render container of the column we're attaching to is
518610 // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
519611 // between the render container and the grid
520 var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
521 var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
522
523 var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
524
525 // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
526 var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
527 var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
528
529 if ( menu.length !== 0 ){
612 var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container'),
613 renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left,
614 containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
615
616 // repositionMenu is now always called after it's visible in the DOM,
617 // allowing us to simply get the width every time the menu is opened
618 var myWidth = gridUtil.elementWidth(menu, true),
619 paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
620
621 if ( menu.length !== 0 ) {
530622 var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
531 if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
532 myWidth = gridUtil.elementWidth(menu, true);
533 $scope.lastMenuWidth = myWidth;
534 column.lastMenuWidth = myWidth;
535
623
624 if ( mid.length !== 0 ) {
536625 // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
537626 // Get the column menu right padding
538627 paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
541630 }
542631 }
543632
544 var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
545 if (left < positionData.offset){
546 left = positionData.offset;
633 var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width + paddingRight;
634
635 if (left < positionData.offset + myWidth) {
636 left = Math.max(positionData.left - containerScrollLeft + positionData.parentLeft - paddingRight + myWidth, positionData.offset + myWidth);
547637 }
548638
549639 $elm.css('left', left + 'px');
550640 $elm.css('top', (positionData.top + positionData.height) + 'px');
551641 }
552
553642 };
554
555643 return service;
556644 }])
557645
565653 *
566654 */
567655
568 var uiGridColumnMenu = {
656 return {
569657 priority: 0,
570658 scope: true,
571659 require: '^uiGrid',
572660 templateUrl: 'ui-grid/uiGridColumnMenu',
573661 replace: true,
574662 link: function ($scope, $elm, $attrs, uiGridCtrl) {
575 var self = this;
576
577663 uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
578664
579665 $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
592678 * to the right place whilst hidden (it will make an assumption on menu width),
593679 * then it asks the menu to show (it will animate), then it repositions the menu again
594680 * once we can calculate it's size.
595 * @param {GridCol} column the column we want to position below
681 * @param {GridColumn} column the column we want to position below
596682 * @param {element} $columnElement the column element we want to position below
597683 */
598684 $scope.showMenu = function(column, $columnElement, event) {
611697
612698 $scope.$broadcast('hide-menu', { originalEvent: event });
613699 } else {
614 self.shown = $scope.menuShown = true;
615 uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
700 $scope.menuShown = true;
616701
617702 $scope.colElement = $columnElement;
618703 $scope.colElementPosition = colElementPosition;
632717 */
633718 $scope.hideMenu = function( broadcastTrigger ) {
634719 $scope.menuShown = false;
635 if ( !broadcastTrigger ){
720 if ( !broadcastTrigger ) {
636721 $scope.$broadcast('hide-menu');
637722 }
638723 };
639724
640725
641726 $scope.$on('menu-hidden', function() {
642 if ( $scope.hideThenShow ){
727 var menuItems = angular.element($elm[0].querySelector('.ui-grid-menu-items'))[0];
728
729 $elm[0].removeAttribute('style');
730
731 if ( $scope.hideThenShow ) {
643732 delete $scope.hideThenShow;
644733
645 uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
646734 $scope.$broadcast('show-menu');
647735
648736 $scope.menuShown = true;
650738 $scope.hideMenu( true );
651739
652740 if ($scope.col) {
653 //Focus on the menu button
741 // Focus on the menu button
654742 gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
655743 }
656744 }
745
746 if (menuItems) {
747 menuItems.onkeydown = null;
748 angular.forEach(menuItems.children, function removeHandlers(item) {
749 item.onkeydown = null;
750 });
751 }
657752 });
658753
659754 $scope.$on('menu-shown', function() {
660 $timeout( function() {
755 $timeout(function() {
661756 uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
757
758 // automatically set the focus to the first button element in the now open menu.
759 gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item:not(.ng-hide)', true);
662760 delete $scope.colElementPosition;
663761 delete $scope.columnElement;
664 }, 200);
762 addKeydownHandlersToMenu();
763 });
665764 });
666765
667766
673772 .then(function () {
674773 $scope.grid.refresh();
675774 $scope.hideMenu();
676 });
775 }).catch(angular.noop);
677776 };
678777
679778 $scope.unsortColumn = function () {
683782 $scope.hideMenu();
684783 };
685784
686 //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
687 var setFocusOnHideColumn = function(){
688 $timeout(function(){
785 function addKeydownHandlersToMenu() {
786 var menu = angular.element($elm[0].querySelector('.ui-grid-menu-items'))[0],
787 menuItems,
788 visibleMenuItems = [];
789
790 if (menu) {
791 menu.onkeydown = function closeMenu(event) {
792 if (event.keyCode === uiGridConstants.keymap.ESC) {
793 event.preventDefault();
794 $scope.hideMenu();
795 }
796 };
797
798 menuItems = menu.querySelectorAll('.ui-grid-menu-item:not(.ng-hide)');
799 angular.forEach(menuItems, function filterVisibleItems(item) {
800 if (item.offsetParent !== null) {
801 this.push(item);
802 }
803 }, visibleMenuItems);
804
805 if (visibleMenuItems.length) {
806 if (visibleMenuItems.length === 1) {
807 visibleMenuItems[0].onkeydown = function singleItemHandler(event) {
808 circularFocusHandler(event, true);
809 };
810 } else {
811 visibleMenuItems[0].onkeydown = function firstItemHandler(event) {
812 circularFocusHandler(event, false, event.shiftKey, visibleMenuItems.length - 1);
813 };
814 visibleMenuItems[visibleMenuItems.length - 1].onkeydown = function lastItemHandler(event) {
815 circularFocusHandler(event, false, !event.shiftKey, 0);
816 };
817 }
818 }
819 }
820
821 function circularFocusHandler(event, isSingleItem, shiftKeyStatus, index) {
822 if (event.keyCode === uiGridConstants.keymap.TAB) {
823 if (isSingleItem) {
824 event.preventDefault();
825 } else if (shiftKeyStatus) {
826 event.preventDefault();
827 visibleMenuItems[index].focus();
828 }
829 }
830 }
831 }
832
833 // Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
834 var setFocusOnHideColumn = function() {
835 $timeout(function() {
689836 // Get the UID of the first
690 var focusToGridMenu = function(){
837 var focusToGridMenu = function() {
691838 return gridUtil.focus.byId('grid-menu', $scope.grid);
692839 };
693840
694841 var thisIndex;
695 $scope.grid.columns.some(function(element, index){
842 $scope.grid.columns.some(function(element, index) {
696843 if (angular.equals(element, $scope.col)) {
697844 thisIndex = index;
698845 return true;
701848
702849 var previousVisibleCol;
703850 // Try and find the next lower or nearest column to focus on
704 $scope.grid.columns.some(function(element, index){
705 if (!element.visible){
851 $scope.grid.columns.some(function(element, index) {
852 if (!element.visible) {
706853 return false;
707854 } // This columns index is below the current column index
708 else if ( index < thisIndex){
855 else if ( index < thisIndex) {
709856 previousVisibleCol = element;
710857 } // This elements index is above this column index and we haven't found one that is lower
711858 else if ( index > thisIndex && !previousVisibleCol) {
720867 }
721868 });
722869 // If found then focus on it
723 if (previousVisibleCol){
870 if (previousVisibleCol) {
724871 var colClass = previousVisibleCol.getColClass();
725 gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
726 if (reason !== 'canceled'){ // If this is canceled then don't perform the action
727 //The fallback action is to focus on the grid menu
872 gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason) {
873 if (reason !== 'canceled') { // If this is canceled then don't perform the action
874 // The fallback action is to focus on the grid menu
728875 return focusToGridMenu();
729876 }
730 });
877 }).catch(angular.noop);
731878 } else {
732879 // Fallback action to focus on the grid menu
733880 focusToGridMenu();
749896 };
750897 },
751898
752
753
754899 controller: ['$scope', function ($scope) {
755900 var self = this;
756901
757 $scope.$watch('menuItems', function (n, o) {
902 $scope.$watch('menuItems', function (n) {
758903 self.menuItems = n;
759904 });
760905 }]
761906 };
762
763 return uiGridColumnMenu;
764
765907 }]);
766
767908 })();
768909
769 (function(){
910 (function() {
770911 'use strict';
771912
772913 angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
774915 return {
775916 compile: function() {
776917 return {
777 pre: function ($scope, $elm, $attrs, controllers) {
778 $scope.col.updateFilters = function( filterable ){
918 pre: function ($scope, $elm) {
919 $scope.col.updateFilters = function( filterable ) {
779920 $elm.children().remove();
780 if ( filterable ){
921 if ( filterable ) {
781922 var template = $scope.col.filterHeaderTemplate;
782
783 $elm.append($compile(template)($scope));
923 if (template === undefined && $scope.col.providedFilterHeaderTemplate !== '') {
924 if ($scope.col.filterHeaderTemplatePromise) {
925 $scope.col.filterHeaderTemplatePromise.then(function () {
926 template = $scope.col.filterHeaderTemplate;
927 $elm.append($compile(template)($scope));
928 });
929 }
930 }
931 else {
932 $elm.append($compile(template)($scope));
933 }
784934 }
785935 };
786936
788938 delete $scope.col.updateFilters;
789939 });
790940 },
791 post: function ($scope, $elm, $attrs, controllers){
941 post: function ($scope, $elm) {
792942 $scope.aria = i18nService.getSafeText('headerCell.aria');
793 $scope.removeFilter = function(colFilter, index){
943 $scope.removeFilter = function(colFilter, index) {
794944 colFilter.term = null;
795 //Set the focus to the filter input after the action disables the button
945 // Set the focus to the filter input after the action disables the button
796946 gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
797947 };
798948 }
807957
808958 angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
809959 function ($timeout, gridUtil, uiGridConstants, $compile) {
810 var uiGridFooterCell = {
960 return {
811961 priority: 0,
812962 scope: {
813963 col: '=',
816966 },
817967 replace: true,
818968 require: '^uiGrid',
819 compile: function compile(tElement, tAttrs, transclude) {
969 compile: function compile() {
820970 return {
821 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
822 var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
823 $elm.append(cellFooter);
971 pre: function ($scope, $elm) {
972 var template = $scope.col.footerCellTemplate;
973
974 if (template === undefined && $scope.col.providedFooterCellTemplate !== '') {
975 if ($scope.col.footerCellTemplatePromise) {
976 $scope.col.footerCellTemplatePromise.then(function () {
977 template = $scope.col.footerCellTemplate;
978 $elm.append($compile(template)($scope));
979 });
980 }
981 }
982 else {
983 $elm.append($compile(template)($scope));
984 }
824985 },
825986 post: function ($scope, $elm, $attrs, uiGridCtrl) {
826 //$elm.addClass($scope.col.getColClass(false));
987 // $elm.addClass($scope.col.getColClass(false));
827988 $scope.grid = uiGridCtrl.grid;
828989
829990 var initColClass = $scope.col.getColClass(false);
991
830992 $elm.addClass(initColClass);
831993
832994 // apply any footerCellClass
833995 var classAdded;
834 var updateClass = function( grid ){
996
997 var updateClass = function() {
835998 var contents = $elm;
836 if ( classAdded ){
999
1000 if ( classAdded ) {
8371001 contents.removeClass( classAdded );
8381002 classAdded = null;
8391003 }
840
1004
8411005 if (angular.isFunction($scope.col.footerCellClass)) {
8421006 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
8431007 }
8461010 }
8471011 contents.addClass(classAdded);
8481012 };
849
1013
8501014 if ($scope.col.footerCellClass) {
8511015 updateClass();
8521016 }
8531017
8541018 $scope.col.updateAggregationValue();
8551019
856 // Watch for column changes so we can alter the col cell class properly
857 /* shouldn't be needed any more, given track by col.name
858 $scope.$watch('col', function (n, o) {
859 if (n !== o) {
860 // See if the column's internal class has changed
861 var newColClass = $scope.col.getColClass(false);
862 if (newColClass !== initColClass) {
863 $elm.removeClass(initColClass);
864 $elm.addClass(newColClass);
865 initColClass = newColClass;
866 }
867 }
868 });
869 */
870
871
8721020 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
8731021 var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
1022
8741023 // listen for visible rows change and update aggregation values
8751024 $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
8761025 $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
8791028 };
8801029 }
8811030 };
882
883 return uiGridFooterCell;
8841031 }]);
885
8861032 })();
8871033
8881034 (function () {
9231069 containerCtrl.footerViewport = footerViewport;
9241070 }
9251071 }
926 });
1072 }).catch(angular.noop);
9271073 },
9281074
9291075 post: function ($scope, $elm, $attrs, controllers) {
9501096 }]);
9511097
9521098 })();
953 (function () {
954 'use strict';
955
956 angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
957
958 return {
959 restrict: 'EA',
960 replace: true,
961 // priority: 1000,
962 require: '^uiGrid',
963 scope: true,
964 compile: function ($elm, $attrs) {
965 return {
966 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
967
968 $scope.grid = uiGridCtrl.grid;
969
970
971
972 var footerTemplate = $scope.grid.options.gridFooterTemplate;
973 gridUtil.getTemplate(footerTemplate)
974 .then(function (contents) {
975 var template = angular.element(contents);
976
977 var newElm = $compile(template)($scope);
978 $elm.append(newElm);
979 });
980 },
981
982 post: function ($scope, $elm, $attrs, controllers) {
983
984 }
985 };
986 }
987 };
988 }]);
989
1099
1100 (function() {
1101 'use strict';
1102
1103 angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil',
1104 function($templateCache, $compile, uiGridConstants, gridUtil) {
1105 return {
1106 restrict: 'EA',
1107 replace: true,
1108 require: '^uiGrid',
1109 scope: true,
1110 compile: function() {
1111 return {
1112 pre: function($scope, $elm, $attrs, uiGridCtrl) {
1113 $scope.grid = uiGridCtrl.grid;
1114
1115 var footerTemplate = $scope.grid.options.gridFooterTemplate;
1116
1117 gridUtil.getTemplate(footerTemplate)
1118 .then(function(contents) {
1119 var template = angular.element(contents),
1120 newElm = $compile(template)($scope);
1121
1122 $elm.append(newElm);
1123 }).catch(angular.noop);
1124 }
1125 };
1126 }
1127 };
1128 }]);
9901129 })();
991 (function(){
992 'use strict';
993
994 angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
995 var defaultTemplate = 'ui-grid/ui-grid-group-panel';
996
997 return {
998 restrict: 'EA',
999 replace: true,
1000 require: '?^uiGrid',
1001 scope: false,
1002 compile: function($elm, $attrs) {
1003 return {
1004 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1005 var groupPanelTemplate = $scope.grid.options.groupPanelTemplate || defaultTemplate;
1006
1007 gridUtil.getTemplate(groupPanelTemplate)
1008 .then(function (contents) {
1009 var template = angular.element(contents);
1010
1011 var newElm = $compile(template)($scope);
1012 $elm.append(newElm);
1013 });
1014 },
1015
1016 post: function ($scope, $elm, $attrs, uiGridCtrl) {
1017 $elm.bind('$destroy', function() {
1018 // scrollUnbinder();
1019 });
1020 }
1021 };
1022 }
1023 };
1024 }]);
1025
1026 })();
1027 (function(){
1130
1131 (function() {
10281132 'use strict';
10291133
10301134 angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
10311135 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
10321136 // Do stuff after mouse has been down this many ms on the header cell
1033 var mousedownTimeout = 500;
1034 var changeModeTimeout = 500; // length of time between a touch event and a mouse event being recognised again, and vice versa
1035
1036 var uiGridHeaderCell = {
1137 var mousedownTimeout = 500,
1138 changeModeTimeout = 500; // length of time between a touch event and a mouse event being recognised again, and vice versa
1139
1140 return {
10371141 priority: 0,
10381142 scope: {
10391143 col: '=',
10441148 replace: true,
10451149 compile: function() {
10461150 return {
1047 pre: function ($scope, $elm, $attrs) {
1048 var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
1049 $elm.append(cellHeader);
1151 pre: function ($scope, $elm) {
1152 var template = $scope.col.headerCellTemplate;
1153 if (template === undefined && $scope.col.providedHeaderCellTemplate !== '') {
1154 if ($scope.col.headerCellTemplatePromise) {
1155 $scope.col.headerCellTemplatePromise.then(function () {
1156 template = $scope.col.headerCellTemplate;
1157 $elm.append($compile(template)($scope));
1158 });
1159 }
1160 }
1161 else {
1162 $elm.append($compile(template)($scope));
1163 }
10501164 },
10511165
10521166 post: function ($scope, $elm, $attrs, controllers) {
10571171 headerCell: i18nService.getSafeText('headerCell'),
10581172 sort: i18nService.getSafeText('sort')
10591173 };
1060 $scope.getSortDirectionAriaLabel = function(){
1174 $scope.isSortPriorityVisible = function() {
1175 // show sort priority if column is sorted and there is at least one other sorted column
1176 return angular.isNumber($scope.col.sort.priority) && $scope.grid.columns.some(function(element, index) {
1177 return angular.isNumber(element.sort.priority) && element !== $scope.col;
1178 });
1179 };
1180 $scope.getSortDirectionAriaLabel = function() {
10611181 var col = $scope.col;
1062 //Trying to recreate this sort of thing but it was getting messy having it in the template.
1063 //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1064 var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1065 var label = sortDirectionText;
1066 //Append the priority if it exists
1067 if (col.sort.priority) {
1068 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
1182 // Trying to recreate this sort of thing but it was getting messy having it in the template.
1183 // Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending': 'none')}}.
1184 // {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1185 var label = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1186
1187 if ($scope.isSortPriorityVisible()) {
1188 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + (col.sort.priority + 1);
10691189 }
10701190 return label;
10711191 };
10851205 $scope.desc = uiGridConstants.DESC;
10861206
10871207 // Store a reference to menu element
1088 var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
1089
10901208 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
10911209
10921210
10931211 // apply any headerCellClass
1094 var classAdded;
1095 var previousMouseX;
1212 var classAdded,
1213 previousMouseX;
10961214
10971215 // filter watchers
10981216 var filterDeregisters = [];
11181236 * will be a click event and that can cause the column menu to close
11191237 *
11201238 */
1121
1122 $scope.downFn = function( event ){
1239 $scope.downFn = function( event ) {
11231240 event.stopPropagation();
11241241
11251242 if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
11391256 if ( $scope.colMenu ) {
11401257 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
11411258 }
1142 });
1259 }).catch(angular.noop);
11431260
11441261 uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
11451262
11461263 $scope.offAllEvents();
1147 if ( event.type === 'touchstart'){
1264 if ( event.type === 'touchstart') {
11481265 $document.on('touchend', $scope.upFn);
11491266 $document.on('touchmove', $scope.moveFn);
1150 } else if ( event.type === 'mousedown' ){
1267 } else if ( event.type === 'mousedown' ) {
11511268 $document.on('mouseup', $scope.upFn);
11521269 $document.on('mousemove', $scope.moveFn);
11531270 }
11541271 };
11551272
1156 $scope.upFn = function( event ){
1273 $scope.upFn = function( event ) {
11571274 event.stopPropagation();
11581275 $timeout.cancel($scope.mousedownTimeout);
11591276 $scope.offAllEvents();
11671284 }
11681285 else {
11691286 // short click
1170 if ( $scope.sortable ){
1287 if ( $scope.sortable ) {
11711288 $scope.handleClick(event);
11721289 }
11731290 }
11741291 };
11751292
1176 $scope.moveFn = function( event ){
1293 $scope.handleKeyDown = function(event) {
1294 if (event.keyCode === 32) {
1295 event.preventDefault();
1296 }
1297 };
1298
1299 $scope.moveFn = function( event ) {
11771300 // Chrome is known to fire some bogus move events.
11781301 var changeValue = event.pageX - previousMouseX;
1179 if ( changeValue === 0 ){ return; }
1302 if ( changeValue === 0 ) { return; }
11801303
11811304 // we're a move, so do nothing and leave for column move (if enabled) to take over
11821305 $timeout.cancel($scope.mousedownTimeout);
11841307 $scope.onDownEvents(event.type);
11851308 };
11861309
1187 $scope.clickFn = function ( event ){
1310 $scope.clickFn = function ( event ) {
11881311 event.stopPropagation();
11891312 $contentsElm.off('click', $scope.clickFn);
11901313 };
11911314
11921315
1193 $scope.offAllEvents = function(){
1316 $scope.offAllEvents = function() {
11941317 $contentsElm.off('touchstart', $scope.downFn);
11951318 $contentsElm.off('mousedown', $scope.downFn);
11961319
12031326 $contentsElm.off('click', $scope.clickFn);
12041327 };
12051328
1206 $scope.onDownEvents = function( type ){
1329 $scope.onDownEvents = function( type ) {
12071330 // If there is a previous event, then wait a while before
12081331 // activating the other mode - i.e. if the last event was a touch event then
12091332 // don't enable mouse events for a wee while (500ms or so)
12101333 // Avoids problems with devices that emulate mouse events when you have touch events
12111334
1212 switch (type){
1335 switch (type) {
12131336 case 'touchmove':
12141337 case 'touchend':
12151338 $contentsElm.on('click', $scope.clickFn);
12161339 $contentsElm.on('touchstart', $scope.downFn);
1217 $timeout(function(){
1340 $timeout(function() {
12181341 $contentsElm.on('mousedown', $scope.downFn);
12191342 }, changeModeTimeout);
12201343 break;
12221345 case 'mouseup':
12231346 $contentsElm.on('click', $scope.clickFn);
12241347 $contentsElm.on('mousedown', $scope.downFn);
1225 $timeout(function(){
1348 $timeout(function() {
12261349 $contentsElm.on('touchstart', $scope.downFn);
12271350 }, changeModeTimeout);
12281351 break;
12341357 };
12351358
12361359
1237 var updateHeaderOptions = function( grid ){
1360 var updateHeaderOptions = function() {
12381361 var contents = $elm;
1239 if ( classAdded ){
1362
1363 if ( classAdded ) {
12401364 contents.removeClass( classAdded );
12411365 classAdded = null;
12421366 }
12491373 }
12501374 contents.addClass(classAdded);
12511375
1252 var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1253 $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
1376 $scope.$applyAsync(function() {
1377 var rightMostContainer = $scope.grid.renderContainers['right'] && $scope.grid.renderContainers['right'].visibleColumnCache.length ?
1378 $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1379 $scope.isLastCol = uiGridCtrl.grid.options && uiGridCtrl.grid.options.enableGridMenu &&
1380 $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ];
1381 });
12541382
12551383 // Figure out whether this column is sortable or not
1256 if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
1257 $scope.sortable = true;
1258 }
1259 else {
1260 $scope.sortable = false;
1261 }
1384 $scope.sortable = Boolean($scope.col.enableSorting);
12621385
12631386 // Figure out whether this column is filterable or not
12641387 var oldFilterable = $scope.filterable;
1265 if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
1266 $scope.filterable = true;
1267 }
1268 else {
1269 $scope.filterable = false;
1270 }
1271
1272 if ( oldFilterable !== $scope.filterable){
1273 if ( typeof($scope.col.updateFilters) !== 'undefined' ){
1388 $scope.filterable = Boolean(uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering);
1389
1390 if ( oldFilterable !== $scope.filterable) {
1391 if ( typeof($scope.col.updateFilters) !== 'undefined' ) {
12741392 $scope.col.updateFilters($scope.filterable);
12751393 }
12761394
12951413 filterDeregister();
12961414 });
12971415 }
1298
12991416 }
13001417
13011418 // figure out whether we support column menus
1302 if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1303 $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
1304 $scope.colMenu = true;
1305 } else {
1306 $scope.colMenu = false;
1307 }
1419 $scope.colMenu = ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1420 $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false);
13081421
13091422 /**
1310 * @ngdoc property
1311 * @name enableColumnMenu
1312 * @propertyOf ui.grid.class:GridOptions.columnDef
1313 * @description if column menus are enabled, controls the column menus for this specific
1314 * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1315 * using this option. If gridOptions.enableColumnMenus === false then you get no column
1316 * menus irrespective of the value of this option ). Defaults to true.
1317 *
1318 */
1423 * @ngdoc property
1424 * @name enableColumnMenu
1425 * @propertyOf ui.grid.class:GridOptions.columnDef
1426 * @description if column menus are enabled, controls the column menus for this specific
1427 * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1428 * using this option. If gridOptions.enableColumnMenus === false then you get no column
1429 * menus irrespective of the value of this option ). Defaults to true.
1430 *
1431 * By default column menu's trigger is hidden before mouse over, but you can always force it to be visible with CSS:
1432 *
1433 * <pre>
1434 * .ui-grid-column-menu-button {
1435 * display: block;
1436 * }
1437 * </pre>
1438 */
13191439 /**
13201440 * @ngdoc property
13211441 * @name enableColumnMenus
13361456 }
13371457 };
13381458
1339 /*
1340 $scope.$watch('col', function (n, o) {
1341 if (n !== o) {
1342 // See if the column's internal class has changed
1343 var newColClass = $scope.col.getColClass(false);
1344 if (newColClass !== initColClass) {
1345 $elm.removeClass(initColClass);
1346 $elm.addClass(newColClass);
1347 initColClass = newColClass;
1348 }
1349 }
1350 });
1351 */
13521459 updateHeaderOptions();
13531460
13541461 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
13681475 .then(function () {
13691476 if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
13701477 uiGridCtrl.grid.refresh();
1371 });
1478 }).catch(angular.noop);
13721479 };
13731480
1481 $scope.headerCellArrowKeyDown = function(event) {
1482 if (event.keyCode === 32 || event.keyCode === 13) {
1483 event.preventDefault();
1484 $scope.toggleMenu(event);
1485 }
1486 };
13741487
13751488 $scope.toggleMenu = function(event) {
1489
13761490 event.stopPropagation();
13771491
13781492 // If the menu is already showing...
13981512 };
13991513 }
14001514 };
1401
1402 return uiGridHeaderCell;
14031515 }]);
1404
14051516 })();
14061517
1407 (function(){
1518 (function() {
14081519 'use strict';
14091520
14101521 angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
14111522 function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
1412 var defaultTemplate = 'ui-grid/ui-grid-header';
1413 var emptyTemplate = 'ui-grid/ui-grid-no-header';
1523 var defaultTemplate = 'ui-grid/ui-grid-header',
1524 emptyTemplate = 'ui-grid/ui-grid-no-header';
14141525
14151526 return {
14161527 restrict: 'EA',
1417 // templateUrl: 'ui-grid/ui-grid-header',
14181528 replace: true,
1419 // priority: 1000,
14201529 require: ['^uiGrid', '^uiGridRenderContainer'],
14211530 scope: true,
1422 compile: function($elm, $attrs) {
1531 compile: function() {
14231532 return {
14241533 pre: function ($scope, $elm, $attrs, controllers) {
1425 var uiGridCtrl = controllers[0];
1426 var containerCtrl = controllers[1];
1534 var uiGridCtrl = controllers[0],
1535 containerCtrl = controllers[1];
14271536
14281537 $scope.grid = uiGridCtrl.grid;
14291538 $scope.colContainer = containerCtrl.colContainer;
14301539
14311540 updateHeaderReferences();
1432
1541
14331542 var headerTemplate;
14341543 if (!$scope.grid.options.showHeader) {
14351544 headerTemplate = emptyTemplate;
14361545 }
14371546 else {
1438 headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;
1547 headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;
14391548 }
14401549
14411550 gridUtil.getTemplate(headerTemplate)
14421551 .then(function (contents) {
14431552 var template = angular.element(contents);
1444
1553
14451554 var newElm = $compile(template)($scope);
14461555 $elm.replaceWith(newElm);
14471556
14651574 }
14661575
14671576 $scope.grid.queueRefresh();
1468 });
1577 }).catch(angular.noop);
14691578
14701579 function updateHeaderReferences() {
14711580 containerCtrl.header = containerCtrl.colContainer.header = $elm;
14801589 }
14811590 }
14821591
1483 function scrollHandler(evt) {
1592 function scrollHandler() {
14841593 if (uiGridCtrl.grid.isScrollingHorizontally) {
14851594 return;
14861595 }
14891598
14901599 var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
14911600 scrollEvent.newScrollLeft = newScrollLeft;
1492 if ( horizScrollPercentage > -1 ){
1601 if ( horizScrollPercentage > -1 ) {
14931602 scrollEvent.x = { percentage: horizScrollPercentage };
14941603 }
14951604
15131622 // already being populated correctly
15141623
15151624 var columnCache = containerCtrl.colContainer.visibleColumnCache;
1516
1625
15171626 // Build the CSS
15181627 // uiGridCtrl.grid.columns.forEach(function (column) {
15191628 var ret = '';
15241633 });
15251634
15261635 containerCtrl.colContainer.canvasWidth = canvasWidth;
1527
1636
15281637 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
15291638 return ret;
15301639 }
1531
1640
15321641 containerCtrl.header = $elm;
1533
1642
15341643 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
15351644 if (headerViewport) {
15361645 containerCtrl.headerViewport = headerViewport;
15371646 }
15381647
1539 //todo: remove this if by injecting gridCtrl into unit tests
1648 // todo: remove this if by injecting gridCtrl into unit tests
15401649 if (uiGridCtrl) {
15411650 uiGridCtrl.grid.registerStyleComputation({
15421651 priority: 15,
15481657 }
15491658 };
15501659 }]);
1551
15521660 })();
15531661
1554 (function(){
1662 (function() {
15551663
15561664 angular.module('ui.grid')
15571665 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
15581666 /**
15591667 * @ngdoc service
1560 * @name ui.grid.gridMenuService
1668 * @name ui.grid.uiGridGridMenuService
15611669 *
15621670 * @description Methods for working with the grid menu
15631671 */
15651673 var service = {
15661674 /**
15671675 * @ngdoc method
1568 * @methodOf ui.grid.gridMenuService
1676 * @methodOf ui.grid.uiGridGridMenuService
15691677 * @name initialize
15701678 * @description Sets up the gridMenu. Most importantly, sets our
15711679 * scope onto the grid object as grid.gridMenuScope, allowing us
15751683 * @param {$scope} $scope the scope of this gridMenu
15761684 * @param {Grid} grid the grid to which this gridMenu is associated
15771685 */
1578 initialize: function( $scope, grid ){
1686 initialize: function( $scope, grid ) {
15791687 grid.gridMenuScope = $scope;
15801688 $scope.grid = grid;
15811689 $scope.registeredMenuItems = [];
15821690
15831691 // not certain this is needed, but would be bad to create a memory leak
15841692 $scope.$on('$destroy', function() {
1585 if ( $scope.grid && $scope.grid.gridMenuScope ){
1693 if ( $scope.grid && $scope.grid.gridMenuScope ) {
15861694 $scope.grid.gridMenuScope = null;
15871695 }
1588 if ( $scope.grid ){
1696 if ( $scope.grid ) {
15891697 $scope.grid = null;
15901698 }
1591 if ( $scope.registeredMenuItems ){
1699 if ( $scope.registeredMenuItems ) {
15921700 $scope.registeredMenuItems = null;
15931701 }
15941702 });
15981706 /**
15991707 * @ngdoc function
16001708 * @name addToGridMenu
1601 * @methodOf ui.grid.core.api:PublicApi
1709 * @methodOf ui.grid.api:PublicApi
16021710 * @description add items to the grid menu. Used by features
16031711 * to add their menu items if they are enabled, can also be used by
16041712 * end users to add menu items. This method has the advantage of allowing
16161724 /**
16171725 * @ngdoc function
16181726 * @name removeFromGridMenu
1619 * @methodOf ui.grid.core.api:PublicApi
1727 * @methodOf ui.grid.api:PublicApi
16201728 * @description Remove an item from the grid menu based on a provided id. Assumes
16211729 * that the id is unique, removes only the last instance of that id. Does nothing if
16221730 * the specified id is not found
16311739 /**
16321740 * @ngdoc function
16331741 * @name addToGridMenu
1634 * @propertyOf ui.grid.gridMenuService
1742 * @propertyOf ui.grid.uiGridGridMenuService
16351743 * @description add items to the grid menu. Used by features
16361744 * to add their menu items if they are enabled, can also be used by
16371745 * end users to add menu items. This method has the advantage of allowing
16391747 * in the menu when. (Noting that in most cases the shown and active functions
16401748 * provide a better way to handle visibility of menu items)
16411749 * @param {Grid} grid the grid on which we are acting
1642 * @param {array} items menu items in the format as described in the tutorial, with
1750 * @param {array} menuItems menu items in the format as described in the tutorial, with
16431751 * the added note that if you want to use remove you must also specify an `id` field,
16441752 * which is provided when you want to remove an item. The id should be unique.
16451753 *
16481756 if ( !angular.isArray( menuItems ) ) {
16491757 gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
16501758 } else {
1651 if ( grid.gridMenuScope ){
1759 if ( grid.gridMenuScope ) {
16521760 grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
16531761 grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
16541762 } else {
16611769 /**
16621770 * @ngdoc function
16631771 * @name removeFromGridMenu
1664 * @methodOf ui.grid.gridMenuService
1772 * @methodOf ui.grid.uiGridGridMenuService
16651773 * @description Remove an item from the grid menu based on a provided id. Assumes
16661774 * that the id is unique, removes only the last instance of that id. Does nothing if
16671775 * the specified id is not found. If there is no gridMenuScope or registeredMenuItems
16711779 * @param {string} id the id we'd like to remove from the menu
16721780 *
16731781 */
1674 removeFromGridMenu: function( grid, id ){
1782 removeFromGridMenu: function( grid, id ) {
16751783 var foundIndex = -1;
16761784
1677 if ( grid && grid.gridMenuScope ){
1785 if ( grid && grid.gridMenuScope ) {
16781786 grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1679 if ( value.id === id ){
1787 if ( value.id === id ) {
16801788 if (foundIndex > -1) {
16811789 gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
16821790 } else {
16871795 });
16881796 }
16891797
1690 if ( foundIndex > -1 ){
1798 if ( foundIndex > -1 ) {
16911799 grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
16921800 }
16931801 },
17151823 */
17161824 /**
17171825 * @ngdoc method
1718 * @methodOf ui.grid.gridMenuService
1826 * @methodOf ui.grid.uiGridGridMenuService
17191827 * @name getMenuItems
17201828 * @description Decides the menu items to show in the menu. This is a
17211829 * combination of:
17281836 * menu
17291837 * @param {$scope} $scope the scope of this gridMenu, from which we can find all
17301838 * the information that we need
1731 * @returns {array} an array of menu items that can be shown
1839 * @returns {Array} an array of menu items that can be shown
17321840 */
17331841 getMenuItems: function( $scope ) {
17341842 var menuItems = [
17351843 // this is where we add any menu items we want to always include
17361844 ];
17371845
1738 if ( $scope.grid.options.gridMenuCustomItems ){
1739 if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1846 if ( $scope.grid.options.gridMenuCustomItems ) {
1847 if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ) {
17401848 gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
17411849 } else {
17421850 menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
17461854 var clearFilters = [{
17471855 title: i18nService.getSafeText('gridMenu.clearAllFilters'),
17481856 action: function ($event) {
1749 $scope.grid.clearAllFilters(undefined, true, undefined);
1857 $scope.grid.clearAllFilters();
17501858 },
17511859 shown: function() {
17521860 return $scope.grid.options.enableFiltering;
17571865
17581866 menuItems = menuItems.concat( $scope.registeredMenuItems );
17591867
1760 if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1868 if ( $scope.grid.options.gridMenuShowHideColumns !== false ) {
17611869 menuItems = menuItems.concat( service.showHideColumns( $scope ) );
17621870 }
17631871
1764 menuItems.sort(function(a, b){
1872 menuItems.sort(function(a, b) {
17651873 return a.order - b.order;
17661874 });
17671875
17891897 */
17901898 /**
17911899 * @ngdoc method
1792 * @methodOf ui.grid.gridMenuService
1900 * @methodOf ui.grid.uiGridGridMenuService
17931901 * @name showHideColumns
17941902 * @description Adds two menu items for each of the columns in columnDefs. One
17951903 * menu item for hide, one menu item for show. Each is visible when appropriate
17971905 * the visible property on the columnDef using toggleColumnVisibility
17981906 * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
17991907 */
1800 showHideColumns: function( $scope ){
1908 showHideColumns: function( $scope ) {
18011909 var showHideColumns = [];
18021910 if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
18031911 return showHideColumns;
18041912 }
18051913
1914 function isColumnVisible(colDef) {
1915 return colDef.visible === true || colDef.visible === undefined;
1916 }
1917
1918 function getColumnIcon(colDef) {
1919 return isColumnVisible(colDef) ? 'ui-grid-icon-ok' : 'ui-grid-icon-cancel';
1920 }
1921
18061922 // add header for columns
18071923 showHideColumns.push({
18081924 title: i18nService.getSafeText('gridMenu.columns'),
1809 order: 300
1925 order: 300,
1926 templateUrl: 'ui-grid/ui-grid-menu-header-item'
18101927 });
18111928
18121929 $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
18131930
1814 $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1815 if ( colDef.enableHiding !== false ){
1931 $scope.grid.options.columnDefs.forEach( function( colDef, index ) {
1932 if ( colDef.enableHiding !== false ) {
18161933 // add hide menu item - shows an OK icon as we only show when column is already visible
18171934 var menuItem = {
1818 icon: 'ui-grid-icon-ok',
1935 icon: getColumnIcon(colDef),
18191936 action: function($event) {
18201937 $event.stopPropagation();
1938
18211939 service.toggleColumnVisibility( this.context.gridCol );
1940
1941 if ($event.target && $event.target.firstChild) {
1942 if (angular.element($event.target)[0].nodeName === 'I') {
1943 $event.target.className = getColumnIcon(this.context.gridCol.colDef);
1944 }
1945 else {
1946 $event.target.firstChild.className = getColumnIcon(this.context.gridCol.colDef);
1947 }
1948 }
18221949 },
18231950 shown: function() {
1824 return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1951 return this.context.gridCol.colDef.enableHiding !== false;
18251952 },
18261953 context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
18271954 leaveOpen: true,
1828 order: 301 + index * 2
1955 order: 301 + index
18291956 };
18301957 service.setMenuItemTitle( menuItem, colDef, $scope.grid );
18311958 showHideColumns.push( menuItem );
1832
1833 // add show menu item - shows no icon as we only show when column is invisible
1834 menuItem = {
1835 icon: 'ui-grid-icon-cancel',
1836 action: function($event) {
1837 $event.stopPropagation();
1838 service.toggleColumnVisibility( this.context.gridCol );
1839 },
1840 shown: function() {
1841 return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1842 },
1843 context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1844 leaveOpen: true,
1845 order: 301 + index * 2 + 1
1846 };
1847 service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1848 showHideColumns.push( menuItem );
18491959 }
18501960 });
18511961 return showHideColumns;
18541964
18551965 /**
18561966 * @ngdoc method
1857 * @methodOf ui.grid.gridMenuService
1967 * @methodOf ui.grid.uiGridGridMenuService
18581968 * @name setMenuItemTitle
18591969 * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
18601970 * item if it returns a string, otherwise waiting for the promise to resolve or reject then
18641974 * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
18651975 *
18661976 */
1867 setMenuItemTitle: function( menuItem, colDef, grid ){
1977 setMenuItemTitle: function( menuItem, colDef, grid ) {
18681978 var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
18691979
1870 if ( typeof(title) === 'string' ){
1980 if ( typeof(title) === 'string' ) {
18711981 menuItem.title = title;
1872 } else if ( title.then ){
1982 } else if ( title.then ) {
18731983 // must be a promise
18741984 menuItem.title = "";
18751985 title.then( function( successValue ) {
18761986 menuItem.title = successValue;
18771987 }, function( errorValue ) {
18781988 menuItem.title = errorValue;
1879 });
1989 }).catch(angular.noop);
18801990 } else {
18811991 gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
18821992 menuItem.title = 'badconfig';
18851995
18861996 /**
18871997 * @ngdoc method
1888 * @methodOf ui.grid.gridMenuService
1998 * @methodOf ui.grid.uiGridGridMenuService
18891999 * @name toggleColumnVisibility
18902000 * @description Toggles the visibility of an individual column. Expects to be
18912001 * provided a context that has on it a gridColumn, which is the column that
18922002 * we'll operate upon. We change the visibility, and refresh the grid as appropriate
1893 * @param {GridCol} gridCol the column that we want to toggle
2003 * @param {GridColumn} gridCol the column that we want to toggle
18942004 *
18952005 */
18962006 toggleColumnVisibility: function( gridCol ) {
19042014
19052015 return service;
19062016 }])
1907
1908
19092017
19102018 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
19112019 function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
19302038 $scope.shown = false;
19312039
19322040 $scope.toggleMenu = function () {
1933 if ( $scope.shown ){
2041 if ( $scope.shown ) {
19342042 $scope.$broadcast('hide-menu');
19352043 $scope.shown = false;
19362044 } else {
19462054 });
19472055 }
19482056 };
1949
19502057 }]);
1951
19522058 })();
19532059
1954 (function(){
2060 (function() {
19552061
19562062 /**
19572063 * @ngdoc directive
19852091
19862092 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
19872093 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
1988 var uiGridMenu = {
2094 return {
19892095 priority: 0,
19902096 scope: {
19912097 // shown: '&',
19962102 templateUrl: 'ui-grid/uiGridMenu',
19972103 replace: false,
19982104 link: function ($scope, $elm, $attrs, uiGridCtrl) {
1999 var self = this;
2000 var menuMid;
2001 var $animate;
2105 $scope.dynamicStyles = '';
2106 if (uiGridCtrl && uiGridCtrl.grid && uiGridCtrl.grid.options && uiGridCtrl.grid.options.gridMenuTemplate) {
2107 var gridMenuTemplate = uiGridCtrl.grid.options.gridMenuTemplate;
2108 gridUtil.getTemplate(gridMenuTemplate).then(function (contents) {
2109 var template = angular.element(contents);
2110 var newElm = $compile(template)($scope);
2111 $elm.replaceWith(newElm);
2112 }).catch(angular.noop);
2113 }
2114
2115 var setupHeightStyle = function(gridHeight) {
2116 // menu appears under header row, so substract that height from it's total
2117 // additional 20px for general padding
2118 var gridMenuMaxHeight = gridHeight - uiGridCtrl.grid.headerHeight - 20;
2119 $scope.dynamicStyles = [
2120 '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
2121 'max-height: ' + gridMenuMaxHeight + 'px;',
2122 '}'
2123 ].join(' ');
2124 };
2125
2126 if (uiGridCtrl) {
2127 setupHeightStyle(uiGridCtrl.grid.gridHeight);
2128 uiGridCtrl.grid.api.core.on.gridDimensionChanged($scope, function(oldGridHeight, oldGridWidth, newGridHeight, newGridWidth) {
2129 setupHeightStyle(newGridHeight);
2130 });
2131 }
20022132
20032133 $scope.i18n = {
20042134 close: i18nService.getSafeText('columnMenu.close')
20052135 };
20062136
20072137 // *** Show/Hide functions ******
2008 self.showMenu = $scope.showMenu = function(event, args) {
2009 if ( !$scope.shown ){
2138 $scope.showMenu = function(event, args) {
2139 if ( !$scope.shown ) {
20102140
20112141 /*
20122142 * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
20222152 */
20232153 $scope.shown = true;
20242154
2025 $timeout( function() {
2155 // Must be a timeout in order to work properly in Firefox. Issue #6533
2156 $timeout(function() {
20262157 $scope.shownMid = true;
20272158 $scope.$emit('menu-shown');
20282159 });
20392170
20402171 // Turn off an existing document click handler
20412172 angular.element(document).off('click touchstart', applyHideMenu);
2173 $elm.off('keyup', checkKeyUp);
2174 $elm.off('keydown', checkKeyDown);
20422175
20432176 // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
20442177 $timeout(function() {
20452178 angular.element(document).on(docEventType, applyHideMenu);
2179 $elm.on('keyup', checkKeyUp);
2180 $elm.on('keydown', checkKeyDown);
20462181 });
2047 //automatically set the focus to the first button element in the now open menu.
2048 gridUtil.focus.bySelector($elm, 'button[type=button]', true);
20492182 };
20502183
20512184
2052 self.hideMenu = $scope.hideMenu = function(event, args) {
2053 if ( $scope.shown ){
2185 $scope.hideMenu = function(event) {
2186 if ( $scope.shown ) {
20542187 /*
20552188 * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
20562189 * set the ng-if (shown = false) after the animation runs. In theory we can cascade off the
20612194 */
20622195 $scope.shownMid = false;
20632196 $timeout( function() {
2064 if ( !$scope.shownMid ){
2197 if ( !$scope.shownMid ) {
20652198 $scope.shown = false;
20662199 $scope.$emit('menu-hidden');
20672200 }
2068 }, 200);
2201 }, 40);
20692202 }
20702203
20712204 angular.element(document).off('click touchstart', applyHideMenu);
2205 $elm.off('keyup', checkKeyUp);
2206 $elm.off('keydown', checkKeyDown);
20722207 };
20732208
20742209 $scope.$on('hide-menu', function (event, args) {
20812216
20822217
20832218 // *** Auto hide when click elsewhere ******
2084 var applyHideMenu = function(){
2219 var applyHideMenu = function() {
20852220 if ($scope.shown) {
20862221 $scope.$apply(function () {
20872222 $scope.hideMenu();
20892224 }
20902225 };
20912226
2227 // close menu on ESC and keep tab cyclical
2228 var checkKeyUp = function(event) {
2229 if (event.keyCode === 27) {
2230 $scope.hideMenu();
2231 }
2232 };
2233
2234 var checkKeyDown = function(event) {
2235 var setFocus = function(elm) {
2236 elm.focus();
2237 event.preventDefault();
2238 return false;
2239 };
2240 if (event.keyCode === 9) {
2241 var firstMenuItem, lastMenuItem;
2242 var menuItemButtons = $elm[0].querySelectorAll('button:not(.ng-hide)');
2243 if (menuItemButtons.length > 0) {
2244 firstMenuItem = menuItemButtons[0];
2245 lastMenuItem = menuItemButtons[menuItemButtons.length - 1];
2246 if (event.target === lastMenuItem && !event.shiftKey) {
2247 setFocus(firstMenuItem);
2248 } else if (event.target === firstMenuItem && event.shiftKey) {
2249 setFocus(lastMenuItem);
2250 }
2251 }
2252 }
2253 };
2254
20922255 if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
20932256 $scope.autoHide = true;
20942257 }
20972260 angular.element($window).on('resize', applyHideMenu);
20982261 }
20992262
2100 $scope.$on('$destroy', function () {
2263 $scope.$on('$destroy', function unbindEvents() {
2264 angular.element($window).off('resize', applyHideMenu);
21012265 angular.element(document).off('click touchstart', applyHideMenu);
2102 });
2103
2104
2105 $scope.$on('$destroy', function() {
2106 angular.element($window).off('resize', applyHideMenu);
2266 $elm.off('keyup', checkKeyUp);
2267 $elm.off('keydown', checkKeyDown);
21072268 });
21082269
21092270 if (uiGridCtrl) {
21112272 }
21122273
21132274 $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
2114 },
2115
2116
2117 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
2118 var self = this;
2119 }]
2275 }
21202276 };
2121
2122 return uiGridMenu;
21232277 }])
21242278
21252279 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
2126 var uiGridMenuItem = {
2280 return {
21272281 priority: 0,
21282282 scope: {
21292283 name: '=',
21362290 leaveOpen: '=',
21372291 screenReaderOnly: '='
21382292 },
2139 require: ['?^uiGrid', '^uiGridMenu'],
2293 require: ['?^uiGrid'],
21402294 templateUrl: 'ui-grid/uiGridMenuItem',
21412295 replace: false,
2142 compile: function($elm, $attrs) {
2296 compile: function() {
21432297 return {
2144 pre: function ($scope, $elm, $attrs, controllers) {
2145 var uiGridCtrl = controllers[0],
2146 uiGridMenuCtrl = controllers[1];
2147
2298 pre: function ($scope, $elm) {
21482299 if ($scope.templateUrl) {
21492300 gridUtil.getTemplate($scope.templateUrl)
21502301 .then(function (contents) {
21522303
21532304 var newElm = $compile(template)($scope);
21542305 $elm.replaceWith(newElm);
2155 });
2306 }).catch(angular.noop);
21562307 }
21572308 },
21582309 post: function ($scope, $elm, $attrs, controllers) {
2159 var uiGridCtrl = controllers[0],
2160 uiGridMenuCtrl = controllers[1];
2310 var uiGridCtrl = controllers[0];
21612311
21622312 // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
21632313 // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
21802330 return $scope.shown.call(context);
21812331 };
21822332
2183 $scope.itemAction = function($event,title) {
2184 gridUtil.logDebug('itemAction');
2333 $scope.itemAction = function($event, title) {
21852334 $event.stopPropagation();
21862335
21872336 if (typeof($scope.action) === 'function') {
21982347
21992348 $scope.action.call(context, $event, title);
22002349
2201 if ( !$scope.leaveOpen ){
2350 if ( !$scope.leaveOpen ) {
22022351 $scope.$emit('hide-menu');
22032352 } else {
2204 /*
2205 * XXX: Fix after column refactor
2206 * Ideally the focus would remain on the item.
2207 * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
2208 */
2209 gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
2353 // Maintain focus on the selected item
2354 var correctParent = $event.target.parentElement;
2355
2356 // nodeName of 'I' means target is i element, need the next parent
2357 if (angular.element($event.target)[0].nodeName === 'I') {
2358 correctParent = correctParent.parentElement;
2359 }
2360
2361 gridUtil.focus.bySelector(correctParent, 'button[type=button]', true);
22102362 }
22112363 }
22122364 };
22132365
2366 $scope.label = function() {
2367 var toBeDisplayed = $scope.name;
2368
2369 if (typeof($scope.name) === 'function') {
2370 toBeDisplayed = $scope.name.call();
2371 }
2372
2373 return toBeDisplayed;
2374 };
2375
22142376 $scope.i18n = i18nService.get();
22152377 }
22162378 };
22172379 }
22182380 };
2219
2220 return uiGridMenuItem;
22212381 }]);
22222382
22232383 })();
22242384
2225 (function(){
2385 (function() {
22262386 'use strict';
22272387 /**
22282388 * @ngdoc overview
22582418 * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
22592419 *
22602420 */
2261 //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
2421 // https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
22622422 var oneBinders = angular.module('ui.grid');
22632423 angular.forEach([
22642424 /**
23612521 * This is done in order to ensure uniqueness of id tags across the grid.
23622522 * This is to prevent two grids in the same document having duplicate id tags.
23632523 */
2364 {tag: 'Id', directiveName:'IdGrid', method: 'attr', appendGridId: true},
2524 {tag: 'Id', directiveName: 'IdGrid', method: 'attr', appendGridId: true},
23652525 /**
23662526 * @ngdoc directive
23672527 * @name ui.grid.directive:uiGridOneBindTitle
23872547 <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text" aria-label="Add this text"></div>
23882548 </pre>
23892549 */
2390 {tag: 'Label', method: 'attr', aria:true},
2550 {tag: 'Label', method: 'attr', aria: true},
23912551 /**
23922552 * @ngdoc directive
23932553 * @name ui.grid.directive:uiGridOneBindAriaLabelledby
24042564 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId" aria-labelledby="gridID32"></div>
24052565 </pre>
24062566 */
2407 {tag: 'Labelledby', method: 'attr', aria:true},
2567 {tag: 'Labelledby', method: 'attr', aria: true},
24082568 /**
24092569 * @ngdoc directive
24102570 * @name ui.grid.directive:uiGridOneBindAriaLabelledbyGrid
24232583 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId" aria-labelledby-Grid="[grid.id]-gridID32"></div>
24242584 </pre>
24252585 */
2426 {tag: 'Labelledby', directiveName:'LabelledbyGrid', appendGridId:true, method: 'attr', aria:true},
2586 {tag: 'Labelledby', directiveName: 'LabelledbyGrid', appendGridId: true, method: 'attr', aria: true},
24272587 /**
24282588 * @ngdoc directive
24292589 * @name ui.grid.directive:uiGridOneBindAriaDescribedby
24402600 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId" aria-describedby="gridID32"></div>
24412601 </pre>
24422602 */
2443 {tag: 'Describedby', method: 'attr', aria:true},
2603 {tag: 'Describedby', method: 'attr', aria: true},
24442604 /**
24452605 * @ngdoc directive
24462606 * @name ui.grid.directive:uiGridOneBindAriaDescribedbyGrid
24592619 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId" aria-describedby="[grid.id]-gridID32"></div>
24602620 </pre>
24612621 */
2462 {tag: 'Describedby', directiveName:'DescribedbyGrid', appendGridId:true, method: 'attr', aria:true}],
2463 function(v){
2622 {tag: 'Describedby', directiveName: 'DescribedbyGrid', appendGridId: true, method: 'attr', aria: true}],
2623 function(v) {
24642624
24652625 var baseDirectiveName = 'uiGridOneBind';
2466 //If it is an aria tag then append the aria label seperately
2467 //This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2468 //If the diretiveName has to be overridden then it does so here. This is because the tag being modified and the directive sometimes don't match up.
2626 // If it is an aria tag then append the aria label seperately
2627 // This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2628 // If the diretiveName has to be overridden then it does so here. This is because the tag being modified and the directive sometimes don't
2629 // match up.
24692630 var directiveName = (v.aria ? baseDirectiveName + 'Aria' : baseDirectiveName) + (v.directiveName ? v.directiveName : v.tag);
2470 oneBinders.directive(directiveName, ['gridUtil', function(gridUtil){
2631 oneBinders.directive(directiveName, ['gridUtil', function(gridUtil) {
24712632 return {
24722633 restrict: 'A',
24732634 require: ['?uiGrid','?^uiGrid'],
2474 link: function(scope, iElement, iAttrs, controllers){
2475 /* Appends the grid id to the beginnig of the value. */
2476 var appendGridId = function(val){
2477 var grid; //Get an instance of the grid if its available
2478 //If its available in the scope then we don't need to try to find it elsewhere
2635 link: function(scope, iElement, iAttrs, controllers) {
2636 /* Appends the grid id to the beginning of the value. */
2637 var appendGridId = function(val) {
2638 var grid; // Get an instance of the grid if its available
2639
2640 // If its available in the scope then we don't need to try to find it elsewhere
24792641 if (scope.grid) {
24802642 grid = scope.grid;
24812643 }
2482 //Another possible location to try to find the grid
2483 else if (scope.col && scope.col.grid){
2644
2645 // Another possible location to try to find the grid
2646 else if (scope.col && scope.col.grid) {
24842647 grid = scope.col.grid;
24852648 }
2486 //Last ditch effort: Search through the provided controllers.
2487 else if (!controllers.some( //Go through the controllers till one has the element we need
2488 function(controller){
2649
2650 // Last ditch effort: Search through the provided controllers.
2651 else if (!controllers.some( // Go through the controllers till one has the element we need
2652 function(controller) {
24892653 if (controller && controller.grid) {
24902654 grid = controller.grid;
2491 return true; //We've found the grid
2655 return true; // We've found the grid
24922656 }
2493 })){
2494 //We tried our best to find it for you
2657 })) {
2658 // We tried our best to find it for you
24952659 gridUtil.logError("["+directiveName+"] A valid grid could not be found to bind id. Are you using this directive " +
24962660 "within the correct scope? Trying to generate id: [gridID]-" + val);
24972661 throw new Error("No valid grid could be found");
24982662 }
24992663
2500 if (grid){
2664 if (grid) {
25012665 var idRegex = new RegExp(grid.id.toString());
2502 //If the grid id hasn't been appended already in the template declaration
2503 if (!idRegex.test(val)){
2666
2667 // If the grid id hasn't been appended already in the template declaration
2668 if (!idRegex.test(val)) {
25042669 val = grid.id.toString() + '-' + val;
25052670 }
25062671 }
25082673 };
25092674
25102675 // The watch returns a function to remove itself.
2511 var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV){
2512 if (newV){
2513 //If we are trying to add an id element then we also apply the grid id if it isn't already there
2676 var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV) {
2677 if (newV) {
2678 // If we are trying to add an id element then we also apply the grid id if it isn't already there
25142679 if (v.appendGridId) {
25152680 var newIdString = null;
2516 //Append the id to all of the new ids.
2517 angular.forEach( newV.split(' '), function(s){
2681
2682 // Append the id to all of the new ids.
2683 angular.forEach( newV.split(' '), function(s) {
25182684 newIdString = (newIdString ? (newIdString + ' ') : '') + appendGridId(s);
25192685 });
25202686 newV = newIdString;
25222688
25232689 // Append this newValue to the dom element.
25242690 switch (v.method) {
2525 case 'attr': //The attr method takes two paraams the tag and the value
2691 case 'attr': // The attr method takes two paraams the tag and the value
25262692 if (v.aria) {
2527 //If it is an aria element then append the aria prefix
2693 // If it is an aria element then append the aria prefix
25282694 iElement[v.method]('aria-' + v.tag.toLowerCase(),newV);
25292695 } else {
25302696 iElement[v.method](v.tag.toLowerCase(),newV);
25312697 }
25322698 break;
25332699 case 'addClass':
2534 //Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
2700 // Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
25352701 if (angular.isObject(newV) && !angular.isArray(newV)) {
2536 var results = [];
2537 var nonNullFound = false; //We don't want to remove the binding unless the key is actually defined
2702 var results = [],
2703 nonNullFound = false; // We don't want to remove the binding unless the key is actually defined
2704
25382705 angular.forEach(newV, function (value, index) {
2539 if (value !== null && typeof(value) !== "undefined"){
2540 nonNullFound = true; //A non null value for a key was found so the object must have been initialized
2706 if (value !== null && typeof(value) !== "undefined") {
2707 nonNullFound = true; // A non null value for a key was found so the object must have been initialized
25412708 if (value) {results.push(index);}
25422709 }
25432710 });
2544 //A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2545 if (!nonNullFound){
2711 // A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2712 if (!nonNullFound) {
25462713 return; // If not initialized then the watcher should not be removed yet.
25472714 }
25482715 newV = results;
25592726 break;
25602727 }
25612728
2562 //Removes the watcher on itself after the bind
2729 // Removes the watcher on itself after the bind
25632730 rmWatcher();
25642731 }
25652732 // True ensures that equality is determined using angular.equals instead of ===
2566 }, true); //End rm watchers
2567 } //End compile function
2568 }; //End directive return
2733 }, true); // End rm watchers
2734 } // End compile function
2735 }; // End directive return
25692736 } // End directive function
2570 ]); //End directive
2737 ]); // End directive
25712738 }); // End angular foreach
25722739 })();
25732740
2574 (function () {
2741 (function() {
2742 'use strict';
2743
2744 var module = angular.module('ui.grid');
2745
2746 module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2747 function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2748 return {
2749 replace: true,
2750 transclude: true,
2751 templateUrl: 'ui-grid/uiGridRenderContainer',
2752 require: ['^uiGrid', 'uiGridRenderContainer'],
2753 scope: {
2754 containerId: '=',
2755 rowContainerName: '=',
2756 colContainerName: '=',
2757 bindScrollHorizontal: '=',
2758 bindScrollVertical: '=',
2759 enableVerticalScrollbar: '=',
2760 enableHorizontalScrollbar: '='
2761 },
2762 controller: 'uiGridRenderContainer as RenderContainer',
2763 compile: function() {
2764 return {
2765 pre: function prelink($scope, $elm, $attrs, controllers) {
2766 var rowContainer, colContainer,
2767 uiGridCtrl = controllers[0],
2768 containerCtrl = controllers[1],
2769 grid = $scope.grid = uiGridCtrl.grid;
2770
2771 // Verify that the render container for this element exists
2772 if (!$scope.rowContainerName) {
2773 throw new Error('No row render container name specified');
2774 }
2775 if (!$scope.colContainerName) {
2776 throw new Error('No column render container name specified');
2777 }
2778
2779 if (!grid.renderContainers[$scope.rowContainerName]) {
2780 throw new Error('Row render container "' + $scope.rowContainerName + '" is not registered.');
2781 }
2782 if (!grid.renderContainers[$scope.colContainerName]) {
2783 throw new Error('Column render container "' + $scope.colContainerName + '" is not registered.');
2784 }
2785
2786 rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2787 colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2788
2789 containerCtrl.containerId = $scope.containerId;
2790 containerCtrl.rowContainer = rowContainer;
2791 containerCtrl.colContainer = colContainer;
2792 },
2793 post: function postlink($scope, $elm, $attrs, controllers) {
2794 var uiGridCtrl = controllers[0],
2795 containerCtrl = controllers[1],
2796 grid = uiGridCtrl.grid,
2797 rowContainer = containerCtrl.rowContainer,
2798 colContainer = containerCtrl.colContainer,
2799 scrollTop = null,
2800 scrollLeft = null,
2801 renderContainer = grid.renderContainers[$scope.containerId];
2802
2803 // Put the container name on this element as a class
2804 $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2805
2806 // Scroll the render container viewport when the mousewheel is used
2807 gridUtil.on.mousewheel($elm, function(event) {
2808 var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2809
2810 if (event.deltaY !== 0) {
2811 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2812
2813 scrollTop = containerCtrl.viewport[0].scrollTop;
2814
2815 // Get the scroll percentage
2816 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2817 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2818
2819 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2820 // Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2821 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2822 containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2823 }
2824
2825 // Keep scrollPercentage within the range 0-1.
2826 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2827 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2828
2829 scrollEvent.y = {percentage: scrollYPercentage, pixels: scrollYAmount};
2830 }
2831 if (event.deltaX !== 0) {
2832 var scrollXAmount = event.deltaX * event.deltaFactor;
2833
2834 // Get the scroll percentage
2835 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2836 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2837 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2838
2839 // Keep scrollPercentage within the range 0-1.
2840 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2841 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2842
2843 scrollEvent.x = {percentage: scrollXPercentage, pixels: scrollXAmount};
2844 }
2845
2846 // Let the parent container scroll if the grid is already at the top/bottom
2847 if (!((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2848 (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft))))) {
2849 event.preventDefault();
2850 event.stopPropagation();
2851 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2852 }
2853 });
2854
2855 $elm.bind('$destroy', function() {
2856 $elm.unbind('keydown');
2857
2858 ['touchstart', 'touchmove', 'touchend', 'keydown', 'wheel', 'mousewheel',
2859 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function(eventName) {
2860 $elm.unbind(eventName);
2861 });
2862 });
2863
2864 // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2865 function update() {
2866 var ret = '';
2867
2868 var canvasWidth = colContainer.canvasWidth;
2869 var viewportWidth = colContainer.getViewportWidth();
2870
2871 var canvasHeight = rowContainer.getCanvasHeight();
2872
2873 var viewportHeight = rowContainer.getViewportHeight();
2874 // shorten the height to make room for a scrollbar placeholder
2875 if (colContainer.needsHScrollbarPlaceholder()) {
2876 viewportHeight -= grid.scrollbarHeight;
2877 }
2878
2879 var headerViewportWidth,
2880 footerViewportWidth;
2881 headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2882
2883 // Set canvas dimensions
2884 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2885 + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2886
2887 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2888 + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2889
2890 if (renderContainer.explicitHeaderCanvasHeight) {
2891 // get height from body container
2892 var reHCHeight = document.querySelector(
2893 '.grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-body .ui-grid-header-canvas');
2894
2895 if (reHCHeight) {
2896 renderContainer.explicitHeaderCanvasHeight = reHCHeight.offsetHeight;
2897 }
2898
2899 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2900 + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2901 }
2902 else {
2903 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2904 + ' .ui-grid-header-canvas { height: inherit; }';
2905 }
2906
2907 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2908 + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2909 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2910 + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2911
2912 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2913 + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2914 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId
2915 + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2916
2917 return ret;
2918 }
2919
2920 uiGridCtrl.grid.registerStyleComputation({
2921 priority: 6,
2922 func: update
2923 });
2924 }
2925 };
2926 }
2927 };
2928 }]);
2929
2930 module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function($scope, gridUtil) {
2931 }]);
2932 })();
2933
2934 (function() {
25752935 'use strict';
25762936
2577 var module = angular.module('ui.grid');
2578
2579 module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2580 function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2937 angular.module('ui.grid').directive('uiGridRow', function() {
25812938 return {
25822939 replace: true,
2583 transclude: true,
2584 templateUrl: 'ui-grid/uiGridRenderContainer',
2585 require: ['^uiGrid', 'uiGridRenderContainer'],
2586 scope: {
2587 containerId: '=',
2588 rowContainerName: '=',
2589 colContainerName: '=',
2590 bindScrollHorizontal: '=',
2591 bindScrollVertical: '=',
2592 enableVerticalScrollbar: '=',
2593 enableHorizontalScrollbar: '='
2594 },
2595 controller: 'uiGridRenderContainer as RenderContainer',
2596 compile: function () {
2597 return {
2598 pre: function prelink($scope, $elm, $attrs, controllers) {
2599
2600 var uiGridCtrl = controllers[0];
2601 var containerCtrl = controllers[1];
2602 var grid = $scope.grid = uiGridCtrl.grid;
2603
2604 // Verify that the render container for this element exists
2605 if (!$scope.rowContainerName) {
2606 throw "No row render container name specified";
2607 }
2608 if (!$scope.colContainerName) {
2609 throw "No column render container name specified";
2610 }
2611
2612 if (!grid.renderContainers[$scope.rowContainerName]) {
2613 throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2614 }
2615 if (!grid.renderContainers[$scope.colContainerName]) {
2616 throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2617 }
2618
2619 var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2620 var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2621
2622 containerCtrl.containerId = $scope.containerId;
2623 containerCtrl.rowContainer = rowContainer;
2624 containerCtrl.colContainer = colContainer;
2625 },
2626 post: function postlink($scope, $elm, $attrs, controllers) {
2627
2628 var uiGridCtrl = controllers[0];
2629 var containerCtrl = controllers[1];
2630
2631 var grid = uiGridCtrl.grid;
2632 var rowContainer = containerCtrl.rowContainer;
2633 var colContainer = containerCtrl.colContainer;
2634 var scrollTop = null;
2635 var scrollLeft = null;
2636
2637
2638 var renderContainer = grid.renderContainers[$scope.containerId];
2639
2640 // Put the container name on this element as a class
2641 $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2642
2643 // Scroll the render container viewport when the mousewheel is used
2644 gridUtil.on.mousewheel($elm, function (event) {
2645 var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2646 if (event.deltaY !== 0) {
2647 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2648
2649 scrollTop = containerCtrl.viewport[0].scrollTop;
2650
2651 // Get the scroll percentage
2652 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2653 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2654
2655 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2656 // Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2657 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2658 containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2659 }
2660
2661 // Keep scrollPercentage within the range 0-1.
2662 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2663 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2664
2665 scrollEvent.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2666 }
2667 if (event.deltaX !== 0) {
2668 var scrollXAmount = event.deltaX * event.deltaFactor;
2669
2670 // Get the scroll percentage
2671 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2672 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2673 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2674
2675 // Keep scrollPercentage within the range 0-1.
2676 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2677 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2678
2679 scrollEvent.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2680 }
2681
2682 // Let the parent container scroll if the grid is already at the top/bottom
2683 if ((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2684 (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft)))) {
2685 //parent controller scrolls
2686 }
2687 else {
2688 event.preventDefault();
2689 event.stopPropagation();
2690 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2691 }
2692
2693 });
2694
2695 $elm.bind('$destroy', function() {
2696 $elm.unbind('keydown');
2697
2698 ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2699 $elm.unbind(eventName);
2700 });
2701 });
2702
2703 // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2704 function update() {
2705 var ret = '';
2706
2707 var canvasWidth = colContainer.canvasWidth;
2708 var viewportWidth = colContainer.getViewportWidth();
2709
2710 var canvasHeight = rowContainer.getCanvasHeight();
2711
2712 //add additional height for scrollbar on left and right container
2713 //if ($scope.containerId !== 'body') {
2714 // canvasHeight -= grid.scrollbarHeight;
2715 //}
2716
2717 var viewportHeight = rowContainer.getViewportHeight();
2718 //shorten the height to make room for a scrollbar placeholder
2719 if (colContainer.needsHScrollbarPlaceholder()) {
2720 viewportHeight -= grid.scrollbarHeight;
2721 }
2722
2723 var headerViewportWidth,
2724 footerViewportWidth;
2725 headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2726
2727 // Set canvas dimensions
2728 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2729
2730 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2731
2732 if (renderContainer.explicitHeaderCanvasHeight) {
2733 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2734 }
2735 else {
2736 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: inherit; }';
2737 }
2738
2739 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2740 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2741
2742 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2743 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2744
2745 return ret;
2746 }
2747
2748 uiGridCtrl.grid.registerStyleComputation({
2749 priority: 6,
2750 func: update
2751 });
2752 }
2753 };
2754 }
2755 };
2756
2757 }]);
2758
2759 module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2760
2761 }]);
2762
2763 })();
2764
2765 (function(){
2766 'use strict';
2767
2768 angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2769 return {
2770 replace: true,
2771 // priority: 2001,
2772 // templateUrl: 'ui-grid/ui-grid-row',
27732940 require: ['^uiGrid', '^uiGridRenderContainer'],
27742941 scope: {
27752942 row: '=uiGridRow',
2776 //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2943 // rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
27772944 rowRenderIndex: '='
27782945 },
27792946 compile: function() {
27822949 var uiGridCtrl = controllers[0];
27832950 var containerCtrl = controllers[1];
27842951
2785 var grid = uiGridCtrl.grid;
2786
27872952 $scope.grid = uiGridCtrl.grid;
27882953 $scope.colContainer = containerCtrl.colContainer;
27892954
28102975 clonedElement = newElm;
28112976 cloneScope = newScope;
28122977 });
2813 });
2978 }).catch(angular.noop);
28142979 }
28152980
28162981 // Initially attach the compiled template to this scope
28232988 }
28242989 });
28252990 },
2826 post: function($scope, $elm, $attrs, controllers) {
2827
2991 post: function($scope, $elm) {
2992 $scope.row.element = $elm;
28282993 }
28292994 };
28302995 }
28312996 };
2832 }]);
2833
2997 });
28342998 })();
2835 (function(){
2999
3000 (function() {
28363001 // 'use strict';
28373002
28383003 /**
28743039
28753040 angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
28763041 return {
2877 // restrict: 'A',
2878 // priority: 1000,
2879 // require: '?^uiGrid',
2880 link: function($scope, $elm, $attrs, uiGridCtrl) {
2881 // gridUtil.logDebug('ui-grid-style link');
2882 // if (uiGridCtrl === undefined) {
2883 // gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
2884 // }
2885
3042 link: function($scope, $elm) {
28863043 var interpolateFn = $interpolate($elm.text(), true);
28873044
28883045 if (interpolateFn) {
28903047 $elm.text(value);
28913048 });
28923049 }
2893
2894 // uiGridCtrl.recalcRowStyles = function() {
2895 // var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2896 // var rowHeight = scope.options.rowHeight;
2897
2898 // var ret = '';
2899 // var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2900 // for (var i = 1; i <= rowStyleCount; i++) {
2901 // ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2902 // offset = offset + rowHeight;
2903 // }
2904
2905 // scope.rowStyles = ret;
2906 // };
2907
2908 // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2909
29103050 }
29113051 };
29123052 }]);
2913
29143053 })();
29153054
2916 (function(){
3055 (function() {
29173056 'use strict';
29183057
2919 angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', '$log',
2920 function(gridUtil, ScrollEvent, uiGridConstants, $log) {
3058 angular.module('ui.grid').directive('uiGridViewport', ['gridUtil', 'ScrollEvent',
3059 function(gridUtil, ScrollEvent) {
29213060 return {
29223061 replace: true,
29233062 scope: {},
29463085 // Register this viewport with its container
29473086 containerCtrl.viewport = $elm;
29483087
2949
2950 $elm.on('scroll', scrollHandler);
3088 /**
3089 * @ngdoc function
3090 * @name customScroller
3091 * @methodOf ui.grid.class:GridOptions
3092 * @description (optional) uiGridViewport.on('scroll', scrollHandler) by default.
3093 * A function that allows you to provide your own scroller function. It is particularly helpful if you want to use third party scrollers
3094 * as this allows you to do that.
3095 *
3096 * <div class="alert alert-info" role="alert"> <strong>NOTE:</strong> It is important to remember to always pass in an event object to
3097 * the scrollHandler as the grid scrolling behavior will break without it.</div>
3098 * <h5>Example</h5>
3099 * <pre>
3100 * $scope.gridOptions = {
3101 * customScroller: function myScrolling(uiGridViewport, scrollHandler) {
3102 * uiGridViewport.on('scroll', function myScrollingOverride(event) {
3103 * // Do something here
3104 *
3105 * scrollHandler(event);
3106 * });
3107 * }
3108 * };
3109 * </pre>
3110 * @param {object} uiGridViewport Element being scrolled. (this gets passed in by the grid).
3111 * @param {function} scrollHandler Function that needs to be called when scrolling happens. (this gets passed in by the grid).
3112 */
3113 if (grid && grid.options && grid.options.customScroller) {
3114 grid.options.customScroller($elm, scrollHandler);
3115 } else {
3116 $elm.on('scroll', scrollHandler);
3117 }
29513118
29523119 var ignoreScroll = false;
29533120
2954 function scrollHandler(evt) {
2955 //Leaving in this commented code in case it can someday be used
2956 //It does improve performance, but because the horizontal scroll is normalized,
2957 // using this code will lead to the column header getting slightly out of line with columns
2958 //
2959 //if (ignoreScroll && (grid.isScrollingHorizontally || grid.isScrollingHorizontally)) {
2960 // //don't ask for scrollTop if we just set it
2961 // ignoreScroll = false;
2962 // return;
2963 //}
2964 //ignoreScroll = true;
2965
3121 function scrollHandler() {
29663122 var newScrollTop = $elm[0].scrollTop;
29673123 var newScrollLeft = gridUtil.normalizeScrollLeft($elm, grid);
29683124
29723128 var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.ViewPortScroll);
29733129 scrollEvent.newScrollLeft = newScrollLeft;
29743130 scrollEvent.newScrollTop = newScrollTop;
2975 if ( horizScrollPercentage > -1 ){
3131 if ( horizScrollPercentage > -1 ) {
29763132 scrollEvent.x = { percentage: horizScrollPercentage };
29773133 }
29783134
2979 if ( vertScrollPercentage > -1 ){
3135 if ( vertScrollPercentage > -1 ) {
29803136 scrollEvent.y = { percentage: vertScrollPercentage };
29813137 }
29823138
29933149 grid.addHorizontalScrollSync($scope.$parent.containerId + 'footer', syncHorizontalFooter);
29943150 }
29953151
2996 function syncVerticalScroll(scrollEvent){
3152 function syncVerticalScroll(scrollEvent) {
29973153 containerCtrl.prevScrollArgs = scrollEvent;
2998 var newScrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
2999 $elm[0].scrollTop = newScrollTop;
3000
3001 }
3002
3003 function syncHorizontalScroll(scrollEvent){
3154 $elm[0].scrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
3155 }
3156
3157 function syncHorizontalScroll(scrollEvent) {
30043158 containerCtrl.prevScrollArgs = scrollEvent;
30053159 var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3160
30063161 $elm[0].scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
30073162 }
30083163
3009 function syncHorizontalHeader(scrollEvent){
3164 function syncHorizontalHeader(scrollEvent) {
30103165 var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3166
30113167 if (containerCtrl.headerViewport) {
30123168 containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
30133169 }
30143170 }
30153171
3016 function syncHorizontalFooter(scrollEvent){
3172 function syncHorizontalFooter(scrollEvent) {
30173173 var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3174
30183175 if (containerCtrl.footerViewport) {
30193176 containerCtrl.footerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
30203177 }
30213178 }
30223179
3023
3180 $scope.$on('$destroy', function unbindEvents() {
3181 $elm.off();
3182 });
30243183 },
30253184 controller: ['$scope', function ($scope) {
3026 this.rowStyle = function (index) {
3185 this.rowStyle = function () {
30273186 var rowContainer = $scope.rowContainer;
30283187 var colContainer = $scope.colContainer;
30293188
30303189 var styles = {};
30313190
3032 if (index === 0 && rowContainer.currentTopRow !== 0) {
3033 // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
3034 var hiddenRowWidth = (rowContainer.currentTopRow) * rowContainer.grid.options.rowHeight;
3035
3036 // return { 'margin-top': hiddenRowWidth + 'px' };
3037 styles['margin-top'] = hiddenRowWidth + 'px';
3191 if (rowContainer.currentTopRow !== 0) {
3192 // top offset based on hidden rows count
3193 var translateY = "translateY("+ (rowContainer.currentTopRow * rowContainer.grid.options.rowHeight) +"px)";
3194
3195 styles['transform'] = translateY;
3196 styles['-webkit-transform'] = translateY;
3197 styles['-ms-transform'] = translateY;
30383198 }
30393199
30403200 if (colContainer.currentFirstColumn !== 0) {
30563216 })();
30573217
30583218 (function() {
3059
3060 angular.module('ui.grid')
3061 .directive('uiGridVisible', function uiGridVisibleAction() {
3062 return function ($scope, $elm, $attr) {
3063 $scope.$watch($attr.uiGridVisible, function (visible) {
3064 // $elm.css('visibility', visible ? 'visible' : 'hidden');
3065 $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3066 });
3067 };
3068 });
3069
3219 angular.module('ui.grid')
3220 .directive('uiGridVisible', function uiGridVisibleAction() {
3221 return function($scope, $elm, $attr) {
3222 $scope.$watch($attr.uiGridVisible, function(visible) {
3223 $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3224 });
3225 };
3226 });
30703227 })();
3228
30713229 (function () {
30723230 'use strict';
30733231
30743232 angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
3075 '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
3233 'gridClassFactory', '$parse', '$compile',
30763234 function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
3077 $templateCache, gridClassFactory, $timeout, $parse, $compile) {
3235 gridClassFactory, $parse, $compile) {
30783236 // gridUtil.logDebug('ui-grid controller');
3079
30803237 var self = this;
3238 var deregFunctions = [];
30813239
30823240 self.grid = gridClassFactory.createGrid($scope.uiGrid);
30833241
3084 //assign $scope.$parent if appScope not already assigned
3242 // assign $scope.$parent if appScope not already assigned
30853243 self.grid.appScope = self.grid.appScope || $scope.$parent;
30863244
30873245 $elm.addClass('grid' + self.grid.id);
30903248
30913249 // angular.extend(self.grid.options, );
30923250
3093 //all properties of grid are available on scope
3251 // all properties of grid are available on scope
30943252 $scope.grid = self.grid;
30953253
30963254 if ($attrs.uiGridColumns) {
3097 $attrs.$observe('uiGridColumns', function(value) {
3098 self.grid.options.columnDefs = value;
3255 deregFunctions.push( $attrs.$observe('uiGridColumns', function(value) {
3256 self.grid.options.columnDefs = angular.isString(value) ? angular.fromJson(value) : value;
30993257 self.grid.buildColumns()
3100 .then(function(){
3258 .then(function() {
31013259 self.grid.preCompileCellTemplates();
31023260
31033261 self.grid.refreshCanvas(true);
3104 });
3105 });
3262 }).catch(angular.noop);
3263 }) );
31063264 }
31073265
3266 // prevents an error from being thrown when the array is not defined yet and fastWatch is on
3267 function getSize(array) {
3268 return array ? array.length : 0;
3269 }
31083270
31093271 // if fastWatch is set we watch only the length and the reference, not every individual object
3110 var deregFunctions = [];
31113272 if (self.grid.options.fastWatch) {
31123273 self.uiGrid = $scope.uiGrid;
31133274 if (angular.isString($scope.uiGrid.data)) {
31143275 deregFunctions.push( $scope.$parent.$watch($scope.uiGrid.data, dataWatchFunction) );
31153276 deregFunctions.push( $scope.$parent.$watch(function() {
3116 if ( self.grid.appScope[$scope.uiGrid.data] ){
3117 return self.grid.appScope[$scope.uiGrid.data].length;
3277 if ( self.grid.appScope[$scope.uiGrid.data] ) {
3278 return self.grid.appScope[$scope.uiGrid.data].length;
31183279 } else {
31193280 return undefined;
3120 }
3281 }
31213282 }, dataWatchFunction) );
31223283 } else {
31233284 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3124 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data.length; }, dataWatchFunction) );
3285 deregFunctions.push( $scope.$parent.$watch(function() { return getSize($scope.uiGrid.data); }, function() { dataWatchFunction($scope.uiGrid.data); }) );
31253286 }
31263287 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3127 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs.length; }, columnDefsWatchFunction) );
3288 deregFunctions.push( $scope.$parent.$watch(function() { return getSize($scope.uiGrid.columnDefs); }, function() { columnDefsWatchFunction($scope.uiGrid.columnDefs); }) );
31283289 } else {
31293290 if (angular.isString($scope.uiGrid.data)) {
31303291 deregFunctions.push( $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction) );
31333294 }
31343295 deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
31353296 }
3136
3297
31373298
31383299 function columnDefsWatchFunction(n, o) {
31393300 if (n && n !== o) {
3140 self.grid.options.columnDefs = n;
3141 self.grid.buildColumns({ orderByColumnDefs: true })
3142 .then(function(){
3143
3144 self.grid.preCompileCellTemplates();
3145
3146 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
3147 });
3301 self.grid.options.columnDefs = $scope.uiGrid.columnDefs;
3302 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN, {
3303 orderByColumnDefs: true,
3304 preCompileCellTemplates: true
3305 });
31483306 }
31493307 }
3308
3309 var mostRecentData;
31503310
31513311 function dataWatchFunction(newData) {
31523312 // gridUtil.logDebug('dataWatch fired');
31533313 var promises = [];
3154
3155 if ( self.grid.options.fastWatch ){
3314
3315 if ( self.grid.options.fastWatch ) {
31563316 if (angular.isString($scope.uiGrid.data)) {
3157 newData = self.grid.appScope[$scope.uiGrid.data];
3317 newData = self.grid.appScope.$eval($scope.uiGrid.data);
31583318 } else {
31593319 newData = $scope.uiGrid.data;
31603320 }
31613321 }
3162
3322
3323 mostRecentData = newData;
3324
31633325 if (newData) {
31643326 // columns length is greater than the number of row header columns, which don't count because they're created automatically
31653327 var hasColumns = self.grid.columns.length > (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0);
31843346 promises.push(self.grid.buildColumns()
31853347 .then(function() {
31863348 self.grid.preCompileCellTemplates();
3187 }));
3349 }).catch(angular.noop));
31883350 }
31893351
31903352 $q.all(promises).then(function() {
3191 self.grid.modifyRows(newData)
3353 // use most recent data, rather than the potentially outdated data passed into watcher handler
3354 self.grid.modifyRows(mostRecentData)
31923355 .then(function () {
31933356 // if (self.viewport) {
31943357 self.grid.redrawInPlace(true);
31983361 self.grid.refreshCanvas(true);
31993362 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
32003363 });
3201 });
3202 });
3364 }).catch(angular.noop);
3365 }).catch(angular.noop);
32033366 }
32043367 }
32053368
32083371 });
32093372
32103373 $scope.$on('$destroy', function() {
3211 deregFunctions.forEach( function( deregFn ){ deregFn(); });
3374 deregFunctions.forEach( function( deregFn ) { deregFn(); });
32123375 styleWatchDereg();
32133376 });
32143377
32153378 self.fireEvent = function(eventName, args) {
3379 args = args || {};
3380
32163381 // Add the grid to the event arguments if it's not there
3217 if (typeof(args) === 'undefined' || args === undefined) {
3218 args = {};
3219 }
3220
3221 if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
3382 if (angular.isUndefined(args.grid)) {
32223383 args.grid = self.grid;
32233384 }
32243385
32283389 self.innerCompile = function innerCompile(elm) {
32293390 $compile(elm)($scope);
32303391 };
3231
32323392 }]);
32333393
32343394 /**
32613421 */
32623422 angular.module('ui.grid').directive('uiGrid', uiGridDirective);
32633423
3264 uiGridDirective.$inject = ['$compile', '$templateCache', '$timeout', '$window', 'gridUtil', 'uiGridConstants'];
3265 function uiGridDirective($compile, $templateCache, $timeout, $window, gridUtil, uiGridConstants) {
3424 uiGridDirective.$inject = ['$window', 'gridUtil', 'uiGridConstants'];
3425 function uiGridDirective($window, gridUtil, uiGridConstants) {
32663426 return {
32673427 templateUrl: 'ui-grid/ui-grid',
32683428 scope: {
33043464 if ($elm[0].offsetWidth <= 0 && sizeChecks < maxSizeChecks) {
33053465 setTimeout(checkSize, sizeCheckInterval);
33063466 sizeChecks++;
3307 }
3308 else {
3309 $timeout(init);
3467 } else {
3468 $scope.$applyAsync(init);
33103469 }
33113470 }
33123471
33133472 // Setup event listeners and watchers
33143473 function setup() {
3474 var deregisterLeftWatcher, deregisterRightWatcher;
3475
33153476 // Bind to window resize events
33163477 angular.element($window).on('resize', gridResize);
33173478
33183479 // Unbind from window resize events when the grid is destroyed
33193480 $elm.on('$destroy', function () {
33203481 angular.element($window).off('resize', gridResize);
3482 deregisterLeftWatcher();
3483 deregisterRightWatcher();
33213484 });
33223485
33233486 // If we add a left container after render, we need to watch and react
3324 $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3487 deregisterLeftWatcher = $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
33253488 if (newValue === oldValue) {
33263489 return;
33273490 }
33293492 });
33303493
33313494 // If we add a right container after render, we need to watch and react
3332 $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3495 deregisterRightWatcher = $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
33333496 if (newValue === oldValue) {
33343497 return;
33353498 }
33473510 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
33483511
33493512 // If the grid isn't tall enough to fit a single row, it's kind of useless. Resize it to fit a minimum number of rows
3350 if (grid.gridHeight < grid.options.rowHeight && grid.options.enableMinHeightCheck) {
3513 if (grid.gridHeight - grid.scrollbarHeight <= grid.options.rowHeight && grid.options.enableMinHeightCheck) {
33513514 autoAdjustHeight();
33523515 }
33533516
33613524 var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
33623525 var headerHeight = grid.options.showHeader ? grid.options.headerRowHeight : 0;
33633526 var footerHeight = grid.calcFooterHeight();
3364
3527
33653528 var scrollbarHeight = 0;
33663529 if (grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
33673530 scrollbarHeight = gridUtil.getScrollbarWidth();
33823545 }
33833546 });
33843547
3385 if (grid.options.enableFiltering) {
3386 var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.every(function(col) {
3548 if (grid.options.enableFiltering && !maxNumberOfFilters) {
3549 var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.length && grid.options.columnDefs.every(function(col) {
33873550 return col.enableFiltering === false;
33883551 });
33893552
33903553 if (!allColumnsHaveFilteringTurnedOff) {
3391 maxNumberOfFilters++;
3554 maxNumberOfFilters = 1;
33923555 }
33933556 }
33943557
34023565 }
34033566
34043567 // Resize the grid on window resize events
3405 function gridResize($event) {
3568 function gridResize() {
34063569 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
34073570 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
34083571
34133576 }
34143577 };
34153578 }
3416
34173579 })();
34183580
3419 (function(){
3581 (function() {
34203582 'use strict';
3421
3422 // TODO: rename this file to ui-grid-pinned-container.js
34233583
34243584 angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
34253585 return {
34263586 restrict: 'EA',
34273587 replace: true,
3428 template: '<div class="ui-grid-pinned-container"><div ui-grid-render-container container-id="side" row-container-name="\'body\'" col-container-name="side" bind-scroll-vertical="true" class="{{ side }} ui-grid-render-container-{{ side }}"></div></div>',
3588 template: '<div class="ui-grid-pinned-container">'
3589 + '<div ui-grid-render-container container-id="side"'
3590 + ' row-container-name="\'body\'" col-container-name="side"'
3591 + ' bind-scroll-vertical="true"'
3592 + ' class="{{ side }} ui-grid-render-container-{{ side }}"></div>'
3593 + '</div>',
34293594 scope: {
34303595 side: '=uiGridPinnedContainer'
34313596 },
35173682 }]);
35183683 })();
35193684
3520 (function(){
3685 (function() {
35213686
35223687 angular.module('ui.grid')
35233688 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout','ScrollEvent',
35253690
35263691 /**
35273692 * @ngdoc object
3528 * @name ui.grid.core.api:PublicApi
3693 * @name ui.grid.api:PublicApi
35293694 * @description Public Api for the core grid features
35303695 *
35313696 */
36133778 self.getRowTemplateFn = null;
36143779
36153780
3616 //representation of the rows on the grid.
3617 //these are wrapped references to the actual data rows (options.data)
3781 // representation of the rows on the grid.
3782 // these are wrapped references to the actual data rows (options.data)
36183783 self.rows = [];
36193784
3620 //represents the columns on the grid
3785 // represents the columns on the grid
36213786 self.columns = [];
36223787
36233788 /**
36403805 * @ngdoc property
36413806 * @name scrollDirection
36423807 * @propertyOf ui.grid.class:Grid
3643 * @description set one of the uiGridConstants.scrollDirection values (UP, DOWN, LEFT, RIGHT, NONE), which tells
3644 * us which direction we are scrolling. Set to NONE via debounced method
3808 * @description set one of the {@link ui.grid.service:uiGridConstants#properties_scrollDirection uiGridConstants.scrollDirection}
3809 * values (UP, DOWN, LEFT, RIGHT, NONE), which tells us which direction we are scrolling.
3810 * Set to NONE via debounced method
36453811 */
36463812 self.scrollDirection = uiGridConstants.scrollDirection.NONE;
36473813
3648 //if true, grid will not respond to any scroll events
3814 // if true, grid will not respond to any scroll events
36493815 self.disableScrolling = false;
36503816
36513817
37083874
37093875 self.scrollbarHeight = 0;
37103876 self.scrollbarWidth = 0;
3711 if (self.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3877 if (self.options.enableHorizontalScrollbar !== uiGridConstants.scrollbars.NEVER) {
37123878 self.scrollbarHeight = gridUtil.getScrollbarWidth();
37133879 }
37143880
3715 if (self.options.enableVerticalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3881 if (self.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER) {
37163882 self.scrollbarWidth = gridUtil.getScrollbarWidth();
37173883 }
3718
3719
37203884
37213885 self.api = new GridApi(self);
37223886
37233887 /**
37243888 * @ngdoc function
37253889 * @name refresh
3726 * @methodOf ui.grid.core.api:PublicApi
3890 * @methodOf ui.grid.api:PublicApi
37273891 * @description Refresh the rendered grid on screen.
37283892 * The refresh method re-runs both the columnProcessors and the
37293893 * rowProcessors, as well as calling refreshCanvas to update all
37333897 * If you only want to resize the grid, not regenerate all the rows
37343898 * and columns, you should consider directly calling refreshCanvas instead.
37353899 *
3900 * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed
37363901 */
37373902 self.api.registerMethod( 'core', 'refresh', this.refresh );
37383903
37393904 /**
37403905 * @ngdoc function
37413906 * @name queueGridRefresh
3742 * @methodOf ui.grid.core.api:PublicApi
3907 * @methodOf ui.grid.api:PublicApi
37433908 * @description Request a refresh of the rendered grid on screen, if multiple
37443909 * calls to queueGridRefresh are made within a digest cycle only one will execute.
37453910 * The refresh method re-runs both the columnProcessors and the
37533918 /**
37543919 * @ngdoc function
37553920 * @name refreshRows
3756 * @methodOf ui.grid.core.api:PublicApi
3921 * @methodOf ui.grid.api:PublicApi
37573922 * @description Runs only the rowProcessors, columns remain as they were.
37583923 * It then calls redrawInPlace and refreshCanvas, which adjust the grid sizing.
37593924 * @returns {promise} promise that is resolved when render completes?
37643929 /**
37653930 * @ngdoc function
37663931 * @name queueRefresh
3767 * @methodOf ui.grid.core.api:PublicApi
3932 * @methodOf ui.grid.api:PublicApi
37683933 * @description Requests execution of refreshCanvas, if multiple requests are made
37693934 * during a digest cycle only one will run. RefreshCanvas updates the grid sizing.
37703935 * @returns {promise} promise that is resolved when render completes?
37753940 /**
37763941 * @ngdoc function
37773942 * @name handleWindowResize
3778 * @methodOf ui.grid.core.api:PublicApi
3943 * @methodOf ui.grid.api:PublicApi
37793944 * @description Trigger a grid resize, normally this would be picked
37803945 * up by a watch on window size, but in some circumstances it is necessary
37813946 * to call this manually
37883953 /**
37893954 * @ngdoc function
37903955 * @name addRowHeaderColumn
3791 * @methodOf ui.grid.core.api:PublicApi
3956 * @methodOf ui.grid.api:PublicApi
37923957 * @description adds a row header column to the grid
37933958 * @param {object} column def
3959 * @param {number} order Determines order of header column on grid. Lower order means header
3960 * is positioned to the left of higher order headers
37943961 *
37953962 */
37963963 self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
37983965 /**
37993966 * @ngdoc function
38003967 * @name scrollToIfNecessary
3801 * @methodOf ui.grid.core.api:PublicApi
3968 * @methodOf ui.grid.api:PublicApi
38023969 * @description Scrolls the grid to make a certain row and column combo visible,
38033970 * in the case that it is not completely visible on the screen already.
38043971 * @param {GridRow} gridRow row to make visible
3805 * @param {GridCol} gridCol column to make visible
3972 * @param {GridColumn} gridCol column to make visible
38063973 * @returns {promise} a promise that is resolved when scrolling is complete
38073974 *
38083975 */
38113978 /**
38123979 * @ngdoc function
38133980 * @name scrollTo
3814 * @methodOf ui.grid.core.api:PublicApi
3981 * @methodOf ui.grid.api:PublicApi
38153982 * @description Scroll the grid such that the specified
38163983 * row and column is in view
38173984 * @param {object} rowEntity gridOptions.data[] array instance to make visible
38233990 /**
38243991 * @ngdoc function
38253992 * @name registerRowsProcessor
3826 * @methodOf ui.grid.core.api:PublicApi
3993 * @methodOf ui.grid.api:PublicApi
38273994 * @description
38283995 * Register a "rows processor" function. When the rows are updated,
38293996 * the grid calls each registered "rows processor", which has a chance
38444011 /**
38454012 * @ngdoc function
38464013 * @name registerColumnsProcessor
3847 * @methodOf ui.grid.core.api:PublicApi
4014 * @methodOf ui.grid.api:PublicApi
38484015 * @description
38494016 * Register a "columns processor" function. When the columns are updated,
38504017 * the grid calls each registered "columns processor", which has a chance
38614028 */
38624029 self.api.registerMethod( 'core', 'registerColumnsProcessor', this.registerColumnsProcessor );
38634030
3864
3865
38664031 /**
38674032 * @ngdoc function
38684033 * @name sortHandleNulls
3869 * @methodOf ui.grid.core.api:PublicApi
4034 * @methodOf ui.grid.api:PublicApi
38704035 * @description A null handling method that can be used when building custom sort
38714036 * functions
38724037 * @example
38734038 * <pre>
38744039 * mySortFn = function(a, b) {
38754040 * var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
3876 * if ( nulls !== null ){
4041 * if ( nulls !== null ) {
38774042 * return nulls;
38784043 * } else {
38794044 * // your code for sorting here
38874052 */
38884053 self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
38894054
3890
38914055 /**
38924056 * @ngdoc function
38934057 * @name sortChanged
3894 * @methodOf ui.grid.core.api:PublicApi
4058 * @methodOf ui.grid.api:PublicApi
38954059 * @description The sort criteria on one or more columns has
38964060 * changed. Provides as parameters the grid and the output of
38974061 * getColumnSorting, which is an array of gridColumns
38984062 * that have sorting on them, sorted in priority order.
38994063 *
39004064 * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
3901 * @param {Function} callBack Will be called when the event is emited. The function passes back an array of columns with
3902 * sorts on them, in priority order.
4065 * @param {Function} callBack Will be called when the event is emited. The function passes back the grid and an array of
4066 * columns with sorts on them, in priority order.
39034067 *
39044068 * @example
39054069 * <pre>
3906 * gridApi.core.on.sortChanged( $scope, function(sortColumns){
4070 * gridApi.core.on.sortChanged( $scope, function(grid, sortColumns) {
39074071 * // do something
39084072 * });
39094073 * </pre>
39134077 /**
39144078 * @ngdoc function
39154079 * @name columnVisibilityChanged
3916 * @methodOf ui.grid.core.api:PublicApi
4080 * @methodOf ui.grid.api:PublicApi
39174081 * @description The visibility of a column has changed,
39184082 * the column itself is passed out as a parameter of the event.
3919 *
4083 *
39204084 * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
39214085 * @param {Function} callBack Will be called when the event is emited. The function passes back the GridCol that has changed.
39224086 *
39324096 /**
39334097 * @ngdoc method
39344098 * @name notifyDataChange
3935 * @methodOf ui.grid.core.api:PublicApi
4099 * @methodOf ui.grid.api:PublicApi
39364100 * @description Notify the grid that a data or config change has occurred,
39374101 * where that change isn't something the grid was otherwise noticing. This
39384102 * might be particularly relevant where you've changed values within the data
39394103 * and you'd like cell classes to be re-evaluated, or changed config within
39404104 * the columnDef and you'd like headerCellClasses to be re-evaluated.
39414105 * @param {string} type one of the
3942 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells
3943 * us which refreshes to fire.
4106 * {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4107 * values (ALL, ROW, EDIT, COLUMN, OPTIONS), which tells us which refreshes to fire.
39444108 *
4109 * - ALL: listeners fired on any of these events, fires listeners on all events.
4110 * - ROW: fired when a row is added or removed.
4111 * - EDIT: fired when the data in a cell is edited.
4112 * - COLUMN: fired when the column definitions are modified.
4113 * - OPTIONS: fired when the grid options are modified.
39454114 */
39464115 self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
39474116
39484117 /**
39494118 * @ngdoc method
39504119 * @name clearAllFilters
3951 * @methodOf ui.grid.core.api:PublicApi
4120 * @methodOf ui.grid.api:PublicApi
39524121 * @description Clears all filters and optionally refreshes the visible rows.
39534122 * @param {object} refreshRows Defaults to true.
39544123 * @param {object} clearConditions Defaults to false.
40324201 * @description Populates columnDefs from the provided data
40334202 * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
40344203 */
4035 Grid.prototype.buildColumnDefsFromData = function (dataRows){
4204 Grid.prototype.buildColumnDefsFromData = function (dataRows) {
40364205 this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
40374206 };
40384207
40474216 Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
40484217 this.rowBuilders.push(rowBuilder);
40494218 };
4050
40514219
40524220 /**
40534221 * @ngdoc function
40724240 *
40734241 * @param {function(grid)} callback function to be called
40744242 * @param {array} types the types of data change you want to be informed of. Values from
4075 * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN, OPTIONS ). Optional and defaults to
4076 * ALL
4243 * the {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4244 * values ( ALL, EDIT, ROW, COLUMN, OPTIONS ). Optional and defaults to ALL
40774245 * @returns {function} deregister function - a function that can be called to deregister this callback
40784246 */
40794247 Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) {
4080 var uid = gridUtil.nextUid();
4081 if ( !types ){
4248 var self = this,
4249 uid = gridUtil.nextUid();
4250
4251 if ( !types ) {
40824252 types = [uiGridConstants.dataChange.ALL];
40834253 }
4084 if ( !Array.isArray(types)){
4254 if ( !Array.isArray(types)) {
40854255 gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
40864256 }
4087 this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this };
4088
4089 var self = this;
4090 var deregisterFunction = function() {
4257 this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this: _this };
4258
4259 return function() {
40914260 delete self.dataChangeCallbacks[uid];
40924261 };
4093 return deregisterFunction;
40944262 };
40954263
40964264 /**
41004268 * @description Calls the callbacks based on the type of data change that
41014269 * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the
41024270 * event type is matching, or if the type is ALL.
4103 * @param {number} type the type of event that occurred - one of the
4104 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4271 * @param {string} type the type of event that occurred - one of the
4272 * {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4273 * values (ALL, ROW, EDIT, COLUMN, OPTIONS)
41054274 */
41064275 Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
4107 angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
4276 angular.forEach( this.dataChangeCallbacks, function( callback, uid ) {
41084277 if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
41094278 callback.types.indexOf( type ) !== -1 ||
41104279 type === uiGridConstants.dataChange.ALL ) {
41114280 if (callback._this) {
4112 callback.callback.apply(callback._this,this);
4281 callback.callback.apply(callback._this, this, options);
41134282 }
41144283 else {
4115 callback.callback( this );
4284 callback.callback(this, options);
41164285 }
41174286 }
41184287 }, this);
41264295 * api for users to tell us when they've changed data or some other event that
41274296 * our watches cannot pick up
41284297 * @param {string} type the type of event that occurred - one of the
4129 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
4298 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4299 *
4300 * - ALL: listeners fired on any of these events, fires listeners on all events.
4301 * - ROW: fired when a row is added or removed.
4302 * - EDIT: fired when the data in a cell is edited.
4303 * - COLUMN: fired when the column definitions are modified.
4304 * - OPTIONS: fired when the grid options are modified.
41304305 */
41314306 Grid.prototype.notifyDataChange = function notifyDataChange(type) {
41324307 var constants = uiGridConstants.dataChange;
4308
41334309 if ( type === constants.ALL ||
41344310 type === constants.COLUMN ||
41354311 type === constants.EDIT ||
41364312 type === constants.ROW ||
4137 type === constants.OPTIONS ){
4313 type === constants.OPTIONS ) {
41384314 this.callDataChangeCallbacks( type );
4139 } else {
4315 }
4316 else {
41404317 gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
41414318 }
41424319 };
4143
41444320
41454321 /**
41464322 * @ngdoc function
41504326 * is notified, which triggers handling of the visible flag.
41514327 * This is called on uiGridConstants.dataChange.COLUMN, and is
41524328 * registered as a dataChangeCallback in grid.js
4153 * @param {string} name column name
4329 * @param {object} grid The grid object.
4330 * @param {object} options Any options passed into the callback.
41544331 */
4155 Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
4156 grid.buildColumns();
4332 Grid.prototype.columnRefreshCallback = function columnRefreshCallback(grid, options) {
4333 grid.buildColumns(options);
41574334 grid.queueGridRefresh();
41584335 };
4159
41604336
41614337 /**
41624338 * @ngdoc function
41654341 * @description calls the row processors, specifically
41664342 * intended to reset the sorting when an edit is called,
41674343 * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
4168 * @param {string} name column name
4344 * @param {object} grid The grid object.
41694345 */
4170 Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
4346 Grid.prototype.processRowsCallback = function processRowsCallback( grid ) {
41714347 grid.queueGridRefresh();
41724348 };
41734349
41784354 * @methodOf ui.grid.class:Grid
41794355 * @description recalculates the footer height,
41804356 * registered as a dataChangeCallback on uiGridConstants.dataChange.OPTIONS
4181 * @param {string} name column name
4357 * @param {object} grid The grid object.
41824358 */
4183 Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ){
4359 Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ) {
41844360 grid.footerHeight = grid.calcFooterHeight();
41854361 grid.columnFooterHeight = grid.calcColumnFooterHeight();
41864362 };
41974373 var columns = this.columns.filter(function (column) {
41984374 return column.colDef.name === name;
41994375 });
4376
42004377 return columns.length > 0 ? columns[0] : null;
42014378 };
42024379
42364413 * Note that if you choose date, your dates should be in a javascript date type
42374414 *
42384415 */
4239 Grid.prototype.assignTypes = function(){
4416 Grid.prototype.assignTypes = function() {
42404417 var self = this;
4418
42414419 self.options.columnDefs.forEach(function (colDef, index) {
4242
4243 //Assign colDef type if not specified
4420 // Assign colDef type if not specified
42444421 if (!colDef.type) {
42454422 var col = new GridColumn(colDef, index, self);
42464423 var firstRow = self.rows.length > 0 ? self.rows[0] : null;
42714448 * @name addRowHeaderColumn
42724449 * @methodOf ui.grid.class:Grid
42734450 * @description adds a row header column to the grid
4274 * @param {object} column def
4451 * @param {object} colDef Column definition object.
4452 * @param {float} order Number that indicates where the column should be placed in the grid.
4453 * @param {boolean} stopColumnBuild Prevents the buildColumn callback from being triggered. This is useful to improve
4454 * performance of the grid during initial load.
42754455 */
4276 Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
4456 Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef, order, stopColumnBuild) {
42774457 var self = this;
4458
4459 // default order
4460 if (order === undefined) {
4461 order = 0;
4462 }
4463
42784464 var rowHeaderCol = new GridColumn(colDef, gridUtil.nextUid(), self);
42794465 rowHeaderCol.isRowHeader = true;
42804466 if (self.isRTL()) {
42894475 // relies on the default column builder being first in array, as it is instantiated
42904476 // as part of grid creation
42914477 self.columnBuilders[0](colDef,rowHeaderCol,self.options)
4292 .then(function(){
4478 .then(function() {
42934479 rowHeaderCol.enableFiltering = false;
42944480 rowHeaderCol.enableSorting = false;
42954481 rowHeaderCol.enableHiding = false;
4482 rowHeaderCol.headerPriority = order;
42964483 self.rowHeaderColumns.push(rowHeaderCol);
4297 self.buildColumns()
4298 .then( function() {
4299 self.preCompileCellTemplates();
4300 self.queueGridRefresh();
4301 });
4302 });
4484 self.rowHeaderColumns = self.rowHeaderColumns.sort(function (a, b) {
4485 return a.headerPriority - b.headerPriority;
4486 });
4487
4488 if (!stopColumnBuild) {
4489 self.buildColumns()
4490 .then(function() {
4491 self.preCompileCellTemplates();
4492 self.queueGridRefresh();
4493 }).catch(angular.noop);
4494 }
4495 }).catch(angular.noop);
43034496 };
43044497
43054498 /**
43094502 * @description returns all columns except for rowHeader columns
43104503 */
43114504 Grid.prototype.getOnlyDataColumns = function getOnlyDataColumns() {
4312 var self = this;
4313 var cols = [];
4505 var self = this,
4506 cols = [];
4507
43144508 self.columns.forEach(function (col) {
43154509 if (self.rowHeaderColumns.indexOf(col) === -1) {
43164510 cols.push(col);
43254519 * @methodOf ui.grid.class:Grid
43264520 * @description creates GridColumn objects from the columnDefinition. Calls each registered
43274521 * columnBuilder to further process the column
4328 * @param {object} options An object contains options to use when building columns
4522 * @param {object} opts An object contains options to use when building columns
43294523 *
43304524 * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions.
43314525 *
43474541 // Remove any columns for which a columnDef cannot be found
43484542 // Deliberately don't use forEach, as it doesn't like splice being called in the middle
43494543 // Also don't cache columns.length, as it will change during this operation
4350 for (i = 0; i < self.columns.length; i++){
4544 for (i = 0; i < self.columns.length; i++) {
43514545 if (!self.getColDef(self.columns[i].name)) {
43524546 self.columns.splice(i, 1);
43534547 i--;
43544548 }
43554549 }
43564550
4357 //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4358 self.rowHeaderColumns.forEach(function (rowHeaderColumn) {
4359 self.columns.unshift(rowHeaderColumn);
4360 });
4361
4551 // add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4552 // rowHeaderColumns is ordered by priority so insert in reverse
4553 for (var j = self.rowHeaderColumns.length - 1; j >= 0; j--) {
4554 self.columns.unshift(self.rowHeaderColumns[j]);
4555 }
43624556
43634557 // look at each column def, and update column properties to match. If the column def
43644558 // doesn't have a column, then splice in a new gridCol
44104604 Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache));
44114605 }
44124606
4413 return $q.all(builderPromises).then(function(){
4414 if (self.rows.length > 0){
4607 return $q.all(builderPromises).then(function() {
4608 if (self.rows.length > 0) {
44154609 self.assignTypes();
44164610 }
4417 });
4611 if (options.preCompileCellTemplates) {
4612 self.preCompileCellTemplates();
4613 }
4614 }).catch(angular.noop);
4615 };
4616
4617 Grid.prototype.preCompileCellTemplate = function(col) {
4618 var self = this;
4619 var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4620 html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4621
4622 col.compiledElementFn = $compile(html);
4623
4624 if (col.compiledElementFnDefer) {
4625 col.compiledElementFnDefer.resolve(col.compiledElementFn);
4626 }
44184627 };
44194628
44204629 /**
44254634 */
44264635 Grid.prototype.preCompileCellTemplates = function() {
44274636 var self = this;
4428
4429 var preCompileTemplate = function( col ) {
4430 var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4431 html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4432
4433 var compiledElementFn = $compile(html);
4434 col.compiledElementFn = compiledElementFn;
4435
4436 if (col.compiledElementFnDefer) {
4437 col.compiledElementFnDefer.resolve(col.compiledElementFn);
4438 }
4439 };
4440
4441 this.columns.forEach(function (col) {
4442 if ( col.cellTemplate ){
4443 preCompileTemplate( col );
4444 } else if ( col.cellTemplatePromise ){
4637 self.columns.forEach(function (col) {
4638 if ( col.cellTemplate ) {
4639 self.preCompileCellTemplate( col );
4640 } else if ( col.cellTemplatePromise ) {
44454641 col.cellTemplatePromise.then( function() {
4446 preCompileTemplate( col );
4447 });
4642 self.preCompileCellTemplate( col );
4643 }).catch(angular.noop);
44484644 }
44494645 });
44504646 };
44574653 * @param {GridColumn} col col object
44584654 */
44594655 Grid.prototype.getQualifiedColField = function (col) {
4460 return 'row.entity.' + gridUtil.preEval(col.field);
4656 var base = 'row.entity';
4657 if ( col.field === uiGridConstants.ENTITY_BINDING ) {
4658 return base;
4659 }
4660 return gridUtil.preEval(base + '.' + col.field);
44614661 };
44624662
44634663 /**
45194719 throw new Error('colDef.name or colDef.field property is required');
45204720 }
45214721
4522 //maintain backwards compatibility with 2.x
4523 //field was required in 2.x. now name is required
4722 // maintain backwards compatibility with 2.x
4723 // field was required in 2.x. now name is required
45244724 if (colDef.name === undefined && colDef.field !== undefined) {
45254725 // See if the column name already exists:
45264726 var newName = colDef.field,
45634763 * @methodOf ui.grid.class:Grid
45644764 * @description returns the GridRow that contains the rowEntity
45654765 * @param {object} rowEntity the gridOptions.data array element instance
4566 * @param {array} rows [optional] the rows to look in - if not provided then
4766 * @param {array} lookInRows [optional] the rows to look in - if not provided then
45674767 * looks in grid.rows
45684768 */
45694769 Grid.prototype.getRow = function getRow(rowEntity, lookInRows) {
46064806 * append to the newRows and add to newHash
46074807 * run the processors
46084808 * ```
4609 *
4809 *
46104810 * Rows are identified using the hashKey if configured. If not configured, then rows
46114811 * are identified using the gridOptions.rowEquality function
4612 *
4812 *
46134813 * This method is useful when trying to select rows immediately after loading data without
46144814 * using a $timeout/$interval, e.g.:
4615 *
4815 *
46164816 * $scope.gridOptions.data = someData;
46174817 * $scope.gridApi.grid.modifyRows($scope.gridOptions.data);
46184818 * $scope.gridApi.selection.selectRow($scope.gridOptions.data[0]);
4619 *
4819 *
46204820 * OR to persist row selection after data update (e.g. rows selected, new data loaded, want
46214821 * originally selected rows to be re-selected))
46224822 */
46244824 var self = this;
46254825 var oldRows = self.rows.slice(0);
46264826 var oldRowHash = self.rowHashMap || self.createRowHashMap();
4827 var allRowsSelected = true;
46274828 self.rowHashMap = self.createRowHashMap();
46284829 self.rows.length = 0;
46294830
46304831 newRawData.forEach( function( newEntity, i ) {
4631 var newRow;
4632 if ( self.options.enableRowHashing ){
4832 var newRow, oldRow;
4833
4834 if ( self.options.enableRowHashing ) {
46334835 // if hashing is enabled, then this row will be in the hash if we already know about it
4634 newRow = oldRowHash.get( newEntity );
4836 oldRow = oldRowHash.get( newEntity );
46354837 } else {
46364838 // otherwise, manually search the oldRows to see if we can find this row
4637 newRow = self.getRow(newEntity, oldRows);
4839 oldRow = self.getRow(newEntity, oldRows);
46384840 }
46394841
4842 // update newRow to have an entity
4843 if ( oldRow ) {
4844 newRow = oldRow;
4845 newRow.entity = newEntity;
4846 }
4847
46404848 // if we didn't find the row, it must be new, so create it
4641 if ( !newRow ){
4849 if ( !newRow ) {
46424850 newRow = self.processRowBuilders(new GridRow(newEntity, i, self));
46434851 }
46444852
46454853 self.rows.push( newRow );
46464854 self.rowHashMap.put( newEntity, newRow );
4855 if (!newRow.isSelected) {
4856 allRowsSelected = false;
4857 }
46474858 });
4859
4860 if (self.selection && self.rows.length) {
4861 self.selection.selectAll = allRowsSelected;
4862 }
46484863
46494864 self.assignTypes();
46504865
46514866 var p1 = $q.when(self.processRowsProcessors(self.rows))
46524867 .then(function (renderableRows) {
46534868 return self.setVisibleRows(renderableRows);
4654 });
4869 }).catch(angular.noop);
46554870
46564871 var p2 = $q.when(self.processColumnsProcessors(self.columns))
46574872 .then(function (renderableColumns) {
46584873 return self.setVisibleColumns(renderableColumns);
4659 });
4874 }).catch(angular.noop);
46604875
46614876 return $q.all([p1, p2]);
46624877 };
46704885 * rowBuilders. this keyword will reference the grid
46714886 */
46724887 Grid.prototype.addRows = function addRows(newRawData) {
4673 var self = this;
4674
4675 var existingRowCount = self.rows.length;
4888 var self = this,
4889 existingRowCount = self.rows.length;
4890
46764891 for (var i = 0; i < newRawData.length; i++) {
46774892 var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
46784893
47124927 * @description registered a styleComputation function
47134928 *
47144929 * If the function returns a value it will be appended into the grid's `<style>` block
4715 * @param {function($scope)} styleComputation function
4930 * @param {function($scope)} styleComputationInfo function
47164931 */
47174932 Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
47184933 this.styleComputations.push(styleComputationInfo);
47194934 };
4720
4721
4722 // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4723 // Grid.prototype.registerRowFilter = function(filter) {
4724 // // TODO(c0bra): validate filter?
4725
4726 // this.rowFilters.push(filter);
4727 // };
4728
4729 // Grid.prototype.removeRowFilter = function(filter) {
4730 // var idx = this.rowFilters.indexOf(filter);
4731
4732 // if (typeof(idx) !== 'undefined' && idx !== undefined) {
4733 // this.rowFilters.slice(idx, 1);
4734 // }
4735 // };
4736
4737 // Grid.prototype.processRowFilters = function(rows) {
4738 // var self = this;
4739 // self.rowFilters.forEach(function (filter) {
4740 // filter.call(self, rows);
4741 // });
4742 // };
4743
47444935
47454936 /**
47464937 * @ngdoc function
47534944 * to alter the set of rows (sorting, etc) as long as the count is not
47544945 * modified.
47554946 *
4756 * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4947 * @param {function(renderedRowsToProcess, columns )} processor rows processor function, which
47574948 * is run in the context of the grid (i.e. this for the function will be the grid), and must
47584949 * return the updated rows list, which is passed to the next processor in the chain
47594950 * @param {number} priority the priority of this processor. In general we try to do them in 100s to leave room
47684959 }
47694960
47704961 this.rowsProcessors.push({processor: processor, priority: priority});
4771 this.rowsProcessors.sort(function sortByPriority( a, b ){
4962 this.rowsProcessors.sort(function sortByPriority( a, b ) {
47724963 return a.priority - b.priority;
47734964 });
47744965 };
47774968 * @ngdoc function
47784969 * @name removeRowsProcessor
47794970 * @methodOf ui.grid.class:Grid
4780 * @param {function(renderableRows)} rows processor function
4971 * @param {function(renderableRows)} processor processor function
47814972 * @description Remove a registered rows processor
47824973 */
47834974 Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
47844975 var idx = -1;
4785 this.rowsProcessors.forEach(function(rowsProcessor, index){
4786 if ( rowsProcessor.processor === processor ){
4976 this.rowsProcessors.forEach(function(rowsProcessor, index) {
4977 if ( rowsProcessor.processor === processor ) {
47874978 idx = index;
47884979 }
47894980 });
47974988 * Private Undocumented Method
47984989 * @name processRowsProcessors
47994990 * @methodOf ui.grid.class:Grid
4800 * @param {Array[GridRow]} The array of "renderable" rows
4801 * @param {Array[GridColumn]} The array of columns
4991 * @param {Array[GridRow]} renderableRows The array of "renderable" rows
48024992 * @description Run all the registered rows processors on the array of renderable rows
48034993 */
48044994 Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
48525042 else {
48535043 finished.resolve(processedRows);
48545044 }
5045 }).catch(function(error) {
5046 throw error;
48555047 });
48565048 }
48575049
48705062
48715063 container.canvasHeightShouldUpdate = true;
48725064
4873 if ( typeof(container.visibleRowCache) === 'undefined' ){
5065 if ( typeof(container.visibleRowCache) === 'undefined' ) {
48745066 container.visibleRowCache = [];
48755067 } else {
48765068 container.visibleRowCache.length = 0;
48885080 self.renderContainers[targetContainer].visibleRowCache.push(row);
48895081 }
48905082 }
5083 self.api.core.raise.rowsVisibleChanged(this.api);
48915084 self.api.core.raise.rowsRendered(this.api);
48925085 };
48935086
48955088 * @ngdoc function
48965089 * @name registerColumnsProcessor
48975090 * @methodOf ui.grid.class:Grid
4898 * @param {function(renderedColumnsToProcess, rows)} columnProcessor column processor function, which
5091 * @param {function(renderedColumnsToProcess, rows)} processor column processor function, which
48995092 * is run in the context of the grid (i.e. this for the function will be the grid), and
49005093 * which must return an updated renderedColumnsToProcess which can be passed to the next processor
49015094 * in the chain
49155108 }
49165109
49175110 this.columnsProcessors.push({processor: processor, priority: priority});
4918 this.columnsProcessors.sort(function sortByPriority( a, b ){
5111 this.columnsProcessors.sort(function sortByPriority( a, b ) {
49195112 return a.priority - b.priority;
49205113 });
49215114 };
49385131 if (self.columnsProcessors.length === 0) {
49395132 return $q.when(myRenderableColumns);
49405133 }
4941
4942 // Counter for iterating through rows processors
4943 var i = 0;
49445134
49455135 // Promise for when we're done with all the processors
49465136 var finished = $q.defer();
49795169 else {
49805170 finished.resolve(myRenderableColumns);
49815171 }
4982 });
5172 }).catch(angular.noop);
49835173 }
49845174
49855175 // Start on the first processor
50225212 * @name handleWindowResize
50235213 * @methodOf ui.grid.class:Grid
50245214 * @description Triggered when the browser window resizes; automatically resizes the grid
5215 * @returns {Promise} A resolved promise once the window resize has completed.
50255216 */
50265217 Grid.prototype.handleWindowResize = function handleWindowResize($event) {
50275218 var self = this;
50295220 self.gridWidth = gridUtil.elementWidth(self.element);
50305221 self.gridHeight = gridUtil.elementHeight(self.element);
50315222
5032 self.queueRefresh();
5223 return self.queueRefresh();
50335224 };
50345225
50355226 /**
50515242
50525243 self.refreshCanceller.then(function () {
50535244 self.refreshCanceller = null;
5054 });
5245 }).catch(angular.noop);
50555246
50565247 return self.refreshCanceller;
50575248 };
50765267
50775268 self.gridRefreshCanceller.then(function () {
50785269 self.gridRefreshCanceller = null;
5079 });
5270 }).catch(angular.noop);
50805271
50815272 return self.gridRefreshCanceller;
50825273 };
51055296 * @methodOf ui.grid.class:Grid
51065297 * @description calls each styleComputation function
51075298 */
5108 // TODO: this used to take $scope, but couldn't see that it was used
51095299 Grid.prototype.buildStyles = function buildStyles() {
5300 var self = this;
5301
51105302 // gridUtil.logDebug('buildStyles');
5111
5112 var self = this;
51135303
51145304 self.customStyles = '';
51155305
51585348 };
51595349
51605350 Grid.prototype.getBodyHeight = function getBodyHeight() {
5161 // Start with the viewportHeight
5162 var bodyHeight = this.getViewportHeight();
5163
5164 // Add the horizontal scrollbar height if there is one
5165 //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5166 // bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
5167 //}
5168
5169 return bodyHeight;
5351 return this.getViewportHeight();
51705352 };
51715353
51725354 // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
51775359 var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
51785360
51795361 // Account for native horizontal scrollbar, if present
5180 //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5181 // viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5182 //}
5362 // if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5363 // viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5364 // }
51835365
51845366 var adjustment = self.getViewportAdjustment();
51855367
51865368 viewPortHeight = viewPortHeight + adjustment.height;
51875369
5188 //gridUtil.logDebug('viewPortHeight', viewPortHeight);
5370 // gridUtil.logDebug('viewPortHeight', viewPortHeight);
51895371
51905372 return viewPortHeight;
51915373 };
51955377
51965378 var viewPortWidth = this.gridWidth;
51975379
5198 //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5199 // viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5200 //}
5380 // if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5381 // viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5382 // }
52015383
52025384 var adjustment = self.getViewportAdjustment();
52035385
52045386 viewPortWidth = viewPortWidth + adjustment.width;
52055387
5206 //gridUtil.logDebug('getviewPortWidth', viewPortWidth);
5388 // gridUtil.logDebug('getviewPortWidth', viewPortWidth);
52075389
52085390 return viewPortWidth;
52095391 };
52105392
52115393 Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5212 var viewPortWidth = this.getViewportWidth();
5213
5214 //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5215 // viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
5216 //}
5217
5218 return viewPortWidth;
5394 return this.getViewportWidth();
52195395 };
52205396
52215397 Grid.prototype.addVerticalScrollSync = function (containerId, callBackFn) {
52335409 * @param scrollEvent
52345410 */
52355411 Grid.prototype.scrollContainers = function (sourceContainerId, scrollEvent) {
5236
52375412 if (scrollEvent.y) {
5238 //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5413 // default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
52395414 var verts = ['body','left', 'right'];
52405415
52415416 this.flagScrollingVertically(scrollEvent);
52605435 }
52615436
52625437 if (scrollEvent.x) {
5263 //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5438 // default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
52645439 var horizs = ['body','bodyheader', 'bodyfooter'];
52655440
52665441 this.flagScrollingHorizontally(scrollEvent);
52745449 this.horizontalScrollSyncCallBackFns[idh](scrollEvent);
52755450 }
52765451 }
5277
52785452 }
5279
52805453 };
52815454
52825455 Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
53045477 };
53055478
53065479 Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
5307 // var count = 0;
5308
5309 // this.rows.forEach(function (row) {
5310 // if (row.visible) {
5311 // count++;
5312 // }
5313 // });
5314
53155480 // return this.visibleRowCache.length;
53165481 return this.renderContainers.body.visibleRowCache.length;
53175482 };
53215486 };
53225487
53235488 Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
5324 // var count = 0;
5325
5326 // this.rows.forEach(function (row) {
5327 // if (row.visible) {
5328 // count++;
5329 // }
5330 // });
5331
53325489 // return this.visibleRowCache.length;
53335490 return this.renderContainers.body.visibleColumnCache.length;
53345491 };
53505507 * @param {GridRow} row Row to access
53515508 * @param {GridColumn} col Column to access
53525509 */
5353 Grid.prototype.getCellValue = function getCellValue(row, col){
5510 Grid.prototype.getCellValue = function getCellValue(row, col) {
53545511 if ( typeof(row.entity[ '$$' + col.uid ]) !== 'undefined' ) {
53555512 return row.entity[ '$$' + col.uid].rendered;
5356 } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ){
5513 }
5514 else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ) {
53575515 return row.entity[col.field];
5358 } else {
5516 }
5517 else {
53595518 if (!col.cellValueGetterCache) {
53605519 col.cellValueGetterCache = $parse(row.getEntityQualifiedColField(col));
53615520 }
53785537
53795538 if (typeof(row.entity['$$' + col.uid]) !== 'undefined') {
53805539 col.cellDisplayGetterCache = $parse(row.entity['$$' + col.uid].rendered + custom_filter);
5381 } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5382 col.cellDisplayGetterCache = $parse(row.entity[col.field] + custom_filter);
5383 } else {
5540 }
5541 else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5542 var colField = col.field.replace(/(')|(\\)/g, "\\$&");
5543
5544 col.cellDisplayGetterCache = $parse('entity[\'' + colField + '\']' + custom_filter);
5545 }
5546 else {
53845547 col.cellDisplayGetterCache = $parse(row.getEntityQualifiedColField(col) + custom_filter);
53855548 }
53865549 }
53875550
5388 return col.cellDisplayGetterCache(row);
5551 var rowWithCol = angular.extend({}, row, {col: col});
5552
5553 return col.cellDisplayGetterCache(rowWithCol);
53895554 };
53905555
53915556
53945559 p = 0;
53955560
53965561 self.columns.forEach(function (col) {
5397 if (col.sort && col.sort.priority && col.sort.priority > p) {
5398 p = col.sort.priority;
5562 if (col.sort && col.sort.priority !== undefined && col.sort.priority >= p) {
5563 p = col.sort.priority + 1;
53995564 }
54005565 });
54015566
5402 return p + 1;
5567 return p;
54035568 };
54045569
54055570 /**
54785643
54795644 if (!add) {
54805645 self.resetColumnSorting(column);
5481 column.sort.priority = 0;
5646 column.sort.priority = undefined;
54825647 // Get the actual priority since there may be columns which have suppressRemoveSort set
54835648 column.sort.priority = self.getNextColumnSortPriority();
54845649 }
5485 else if (!column.sort.priority){
5650 else if (column.sort.priority === undefined) {
54865651 column.sort.priority = self.getNextColumnSortPriority();
54875652 }
54885653
55005665 if (column.sortDirectionCycle[i]) {
55015666 column.sort.direction = column.sortDirectionCycle[i];
55025667 } else {
5503 column.sort = {};
5668 removeSortOfColumn(column, self);
55045669 }
55055670 }
55065671 else {
55125677 return $q.when(column);
55135678 };
55145679
5680 var removeSortOfColumn = function removeSortOfColumn(column, grid) {
5681 // Decrease priority for every col where priority is higher than the removed sort's priority.
5682 grid.columns.forEach(function (col) {
5683 if (col.sort && col.sort.priority !== undefined && col.sort.priority > column.sort.priority) {
5684 col.sort.priority -= 1;
5685 }
5686 });
5687
5688 // Remove sort
5689 column.sort = {};
5690 };
5691
55155692 /**
55165693 * communicate to outside world that we are done with initial rendering
55175694 */
5518 Grid.prototype.renderingComplete = function(){
5695 Grid.prototype.renderingComplete = function() {
55195696 if (angular.isFunction(this.options.onRegisterApi)) {
55205697 this.options.onRegisterApi(this.api);
55215698 }
55445721
55455722 var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
55465723 self.setVisibleRows(renderableRows);
5547 });
5724 }).catch(angular.noop);
55485725
55495726 var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
55505727 self.setVisibleColumns(renderableColumns);
5551 });
5728 }).catch(angular.noop);
55525729
55535730 return $q.all([p1, p2]).then(function () {
5731 self.refreshCanvas(true);
55545732 self.redrawInPlace(rowsAltered);
5555
5556 self.refreshCanvas(true);
5557 });
5733 }).catch(angular.noop);
55585734 };
55595735
55605736 /**
55755751 self.redrawInPlace();
55765752
55775753 self.refreshCanvas( true );
5578 });
5754 }).catch(angular.noop);
55795755 };
55805756
55815757 /**
55915767 Grid.prototype.refreshCanvas = function(buildStyles) {
55925768 var self = this;
55935769
5594 if (buildStyles) {
5595 self.buildStyles();
5596 }
5770 // gridUtil.logDebug('refreshCanvas');
55975771
55985772 var p = $q.defer();
55995773
56155789 containerHeadersToRecalc.push(container);
56165790 }
56175791 }
5792 }
5793
5794 // Build the styles without the explicit header heights
5795 if (buildStyles) {
5796 self.buildStyles();
56185797 }
56195798
56205799 /*
56305809 *
56315810 */
56325811 if (containerHeadersToRecalc.length > 0) {
5633 // Build the styles without the explicit header heights
5634 if (buildStyles) {
5635 self.buildStyles();
5636 }
5637
56385812 // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
56395813 $timeout(function() {
56405814 // var oldHeaderHeight = self.grid.headerHeight;
56465820 var maxHeaderHeight = 0;
56475821 var maxHeaderCanvasHeight = 0;
56485822 var i, container;
5649 var getHeight = function(oldVal, newVal){
5650 if ( oldVal !== newVal){
5823 var getHeight = function(oldVal, newVal) {
5824 if ( oldVal !== newVal) {
56515825 rebuildStyles = true;
56525826 }
56535827 return newVal;
56615835 }
56625836
56635837 if (container.header) {
5664 var headerHeight = container.headerHeight = getHeight(container.headerHeight, parseInt(gridUtil.outerElementHeight(container.header), 10));
5838 var headerHeight = container.headerHeight = getHeight(container.headerHeight, gridUtil.outerElementHeight(container.header));
56655839
56665840 // Get the "inner" header height, that is the height minus the top and bottom borders, if present. We'll use it to make sure all the headers have a consistent height
56675841 var topBorder = gridUtil.getBorderSize(container.header, 'top');
57395913 return p.promise;
57405914 };
57415915
5916 function getPrevScrollValue(rowsAdded, prevScrollVal) {
5917 return rowsAdded || prevScrollVal > 0 ? prevScrollVal : null;
5918 }
57425919
57435920 /**
57445921 * @ngdoc function
5745 * @name redrawCanvas
5922 * @name redrawInPlace
57465923 * @methodOf ui.grid.class:Grid
57475924 * @description Redraw the rows and columns based on our current scroll position
57485925 * @param {boolean} [rowsAdded] Optional to indicate rows are added and the scroll percentage must be recalculated
57545931 var self = this;
57555932
57565933 for (var i in self.renderContainers) {
5757 var container = self.renderContainers[i];
5934 var container = self.renderContainers[i],
5935 prevScrollTop = getPrevScrollValue(rowsAdded, container.prevScrollTop),
5936 prevScrollLeft = getPrevScrollValue(rowsAdded, container.prevScrollLeft),
5937 prevScrolltopPercentage = rowsAdded || prevScrollTop > 0 ? null : container.prevScrolltopPercentage,
5938 prevScrollleftPercentage = rowsAdded || prevScrollLeft > 0 ? null : container.prevScrollleftPercentage;
57585939
57595940 // gridUtil.logDebug('redrawing container', i);
57605941
5761 if (rowsAdded) {
5762 container.adjustRows(container.prevScrollTop, null);
5763 container.adjustColumns(container.prevScrollLeft, null);
5764 }
5765 else {
5766 container.adjustRows(null, container.prevScrolltopPercentage);
5767 container.adjustColumns(null, container.prevScrollleftPercentage);
5768 }
5942 container.adjustRows(prevScrollTop, prevScrolltopPercentage);
5943 container.adjustColumns(prevScrollLeft, prevScrollleftPercentage);
57695944 }
57705945 };
57715946
57895964 return this.hasRightContainer() && this.renderContainers.right.renderedColumns.length > 0;
57905965 };
57915966
5967 // Turn the scroll position into a percentage and make it an argument for a scroll event
5968 function getScrollPercentage(scrollPixels, scrollLength) {
5969 var percentage = scrollPixels / scrollLength;
5970
5971 // if the percentage is greater than 1, set it to 1
5972 return percentage <= 1 ? percentage : 1;
5973 }
5974
5975 // Only returns the scroll Y position if the percentage is different from the previous
5976 function getScrollY(scrollPixels, scrollLength, prevScrolltopPercentage) {
5977 var scrollPercentage = getScrollPercentage(scrollPixels, scrollLength);
5978
5979 if (scrollPercentage !== prevScrolltopPercentage) {
5980 return { percentage: getScrollPercentage(scrollPixels, scrollLength) };
5981 }
5982
5983 return undefined;
5984 }
5985
5986 // Only returns the scroll X position if the percentage is different from the previous
5987 function getScrollX(horizScrollPixels, horizScrollLength, prevScrollleftPercentage) {
5988 var horizPercentage = horizScrollPixels / horizScrollLength;
5989 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5990
5991 if (horizPercentage !== prevScrollleftPercentage) {
5992 return { percentage: horizPercentage };
5993 }
5994
5995 return undefined;
5996 }
5997
57925998 /**
57935999 * @ngdoc method
57946000 * @methodOf ui.grid.class:Grid
57966002 * @description Scrolls the grid to make a certain row and column combo visible,
57976003 * in the case that it is not completely visible on the screen already.
57986004 * @param {GridRow} gridRow row to make visible
5799 * @param {GridCol} gridCol column to make visible
6005 * @param {GridColumn} gridCol column to make visible
58006006 * @returns {promise} a promise that is resolved when scrolling is complete
58016007 */
58026008 Grid.prototype.scrollToIfNecessary = function (gridRow, gridCol) {
58216027
58226028 // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
58236029 // Basically this is the viewport height added on to the scroll position
5824 var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight - self.scrollbarWidth;
6030 var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight - self.scrollbarHeight;
58256031
58266032 // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
5827 //if (self.horizontalScrollbarHeight) {
5828 // bottomBound = bottomBound - self.horizontalScrollbarHeight;
5829 //}
6033 // if (self.horizontalScrollbarHeight) {
6034 // bottomBound = bottomBound - self.horizontalScrollbarHeight;
6035 // }
58306036
58316037 // The right position is the current X scroll position minus the grid width
58326038 var rightBound = self.renderContainers.body.prevScrollLeft + Math.ceil(self.renderContainers.body.getViewportWidth());
58336039
58346040 // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
5835 //if (self.verticalScrollbarWidth) {
5836 // rightBound = rightBound - self.verticalScrollbarWidth;
5837 //}
6041 // if (self.verticalScrollbarWidth) {
6042 // rightBound = rightBound - self.verticalScrollbarWidth;
6043 // }
58386044
58396045 // We were given a row to scroll to
58406046 if (gridRow !== null) {
58456051 var scrollLength = (self.renderContainers.body.getCanvasHeight() - self.renderContainers.body.getViewportHeight());
58466052
58476053 // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5848 //if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
5849 // scrollLength = scrollLength + self.horizontalScrollbarHeight;
5850 //}
6054 // if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
6055 // scrollLength = scrollLength + self.horizontalScrollbarHeight;
6056 // }
58516057
58526058 // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
5853 var pixelsToSeeRow = ((seekRowIndex + 1) * self.options.rowHeight);
6059 var pixelsToSeeRow = (seekRowIndex * self.options.rowHeight + self.headerHeight);
58546060
58556061 // Don't let the pixels required to see the row be less than zero
58566062 pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
58576063
5858 var scrollPixels, percentage;
6064 var scrollPixels;
58596065
58606066 // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the self...
5861 if (pixelsToSeeRow < topBound) {
6067 if (pixelsToSeeRow < Math.floor(topBound)) {
58626068 // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
58636069 // to get the full position we need
58646070 scrollPixels = self.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
58656071
5866 // Turn the scroll position into a percentage and make it an argument for a scroll event
5867 percentage = scrollPixels / scrollLength;
5868 scrollEvent.y = { percentage: percentage };
6072 // Since scrollIfNecessary is called multiple times when enableCellEditOnFocus is true we need to make sure the scrollbarWidth and
6073 // footerHeight is accounted for to not cause a loop.
6074 if (gridCol && gridCol.colDef && gridCol.colDef.enableCellEditOnFocus) {
6075 scrollPixels = scrollPixels - self.footerHeight - self.scrollbarHeight;
6076 }
6077
6078 scrollEvent.y = getScrollY(scrollPixels, scrollLength, self.renderContainers.body.prevScrolltopPercentage);
58696079 }
58706080 // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the self...
5871 else if (pixelsToSeeRow > bottomBound) {
6081 else if (pixelsToSeeRow > Math.ceil(bottomBound)) {
58726082 // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
58736083 // to get the full position we need
58746084 scrollPixels = pixelsToSeeRow - bottomBound + self.renderContainers.body.prevScrollTop;
58756085
5876 // Turn the scroll position into a percentage and make it an argument for a scroll event
5877 percentage = scrollPixels / scrollLength;
5878 scrollEvent.y = { percentage: percentage };
6086 scrollEvent.y = getScrollY(scrollPixels, scrollLength,self.renderContainers.body.prevScrolltopPercentage);
58796087 }
58806088 }
58816089
58826090 // We were given a column to scroll to
58836091 if (gridCol !== null) {
5884 // This is the index of the row we want to scroll to, within the list of rows that can be visible
6092 // This is the index of the column we want to scroll to, within the list of columns that can be visible
58856093 var seekColumnIndex = visColCache.indexOf(gridCol);
58866094
5887 // Total vertical scroll length of the grid
6095 // Total horizontal scroll length of the grid
58886096 var horizScrollLength = (self.renderContainers.body.getCanvasWidth() - self.renderContainers.body.getViewportWidth());
58896097
5890 // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
5891 // if (self.verticalScrollbarWidth && self.verticalScrollbarWidth > 0) {
5892 // horizScrollLength = horizScrollLength + self.verticalScrollbarWidth;
5893 // }
5894
5895 // This is the minimum amount of pixels we need to scroll vertical in order to see this column
6098 // This is the minimum amount of pixels we need to scroll horizontal in order to see this column
58966099 var columnLeftEdge = 0;
58976100 for (var i = 0; i < seekColumnIndex; i++) {
58986101 var col = visColCache[i];
59056108 // Don't let the pixels required to see the column be less than zero
59066109 columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
59076110
5908 var horizScrollPixels, horizPercentage;
5909
5910 // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the self...
6111 var horizScrollPixels;
6112
6113 // If the scroll position we need to see the column is LESS than the left boundary, i.e. obscured before the left of the self...
59116114 if (columnLeftEdge < leftBound) {
5912 // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
6115 // Get the different between the left boundary and the required scroll position and subtract it from the current scroll position\
59136116 // to get the full position we need
59146117 horizScrollPixels = self.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
59156118
59166119 // Turn the scroll position into a percentage and make it an argument for a scroll event
5917 horizPercentage = horizScrollPixels / horizScrollLength;
5918 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5919 scrollEvent.x = { percentage: horizPercentage };
5920 }
5921 // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the self...
6120 scrollEvent.x = getScrollX(horizScrollPixels, horizScrollLength, self.renderContainers.body.prevScrollleftPercentage);
6121 }
6122 // Otherwise if the scroll position we need to see the column is MORE than the right boundary, i.e. obscured after the right of the self...
59226123 else if (columnRightEdge > rightBound) {
5923 // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
6124 // Get the different between the right boundary and the required scroll position and add it to the current scroll position
59246125 // to get the full position we need
59256126 horizScrollPixels = columnRightEdge - rightBound + self.renderContainers.body.prevScrollLeft;
59266127
59276128 // Turn the scroll position into a percentage and make it an argument for a scroll event
5928 horizPercentage = horizScrollPixels / horizScrollLength;
5929 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
5930 scrollEvent.x = { percentage: horizPercentage };
6129 scrollEvent.x = getScrollX(horizScrollPixels, horizScrollLength, self.renderContainers.body.prevScrollleftPercentage);
59316130 }
59326131 }
59336132
60466245 }
60476246 };
60486247
6049
6050
60516248 return Grid;
6052
60536249 }]);
6054
60556250 })();
60566251
60576252 (function () {
60586253
60596254 angular.module('ui.grid')
6060 .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
6061 function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
6255 .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow',
6256 function ($q, $rootScope, gridUtil, uiGridConstants, GridRow) {
60626257 /**
60636258 * @ngdoc function
60646259 * @name ui.grid.class:GridApi
60656260 * @description GridApi provides the ability to register public methods events inside the grid and allow
6066 * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
6261 * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args) {}.
60676262 * <br/>
60686263 * To listen to events, you must add a callback to gridOptions.onRegisterApi
60696264 * <pre>
6070 * $scope.gridOptions.onRegisterApi = function(gridApi){
6071 * gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
6265 * $scope.gridOptions.onRegisterApi = function(gridApi) {
6266 * gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol) {
60726267 * $log.log('navigation event');
60736268 * });
60746269 * };
60786273 var GridApi = function GridApi(grid) {
60796274 this.grid = grid;
60806275 this.listeners = [];
6081
6276
60826277 /**
60836278 * @ngdoc function
60846279 * @name renderingComplete
6085 * @methodOf ui.grid.core.api:PublicApi
6280 * @methodOf ui.grid.api:PublicApi
60866281 * @description Rendering is complete, called at the same
60876282 * time as `onRegisterApi`, but provides a way to obtain
60886283 * that same event within features without stopping end
60896284 * users from getting at the onRegisterApi method.
6090 *
6285 *
60916286 * Included in gridApi so that it's always there - otherwise
60926287 * there is still a timing problem with when a feature can
6093 * call this.
6094 *
6095 * @param {GridApi} gridApi the grid api, as normally
6288 * call this.
6289 *
6290 * @param {GridApi} gridApi the grid api, as normally
60966291 * returned in the onRegisterApi method
6097 *
6292 *
60986293 * @example
60996294 * <pre>
61006295 * gridApi.core.on.renderingComplete( grid );
61056300 /**
61066301 * @ngdoc event
61076302 * @name filterChanged
6108 * @eventOf ui.grid.core.api:PublicApi
6303 * @eventOf ui.grid.api:PublicApi
61096304 * @description is raised after the filter is changed. The nature
61106305 * of the watch expression doesn't allow notification of what changed,
6111 * so the receiver of this event will need to re-extract the filter
6306 * so the receiver of this event will need to re-extract the filter
61126307 * conditions from the columns.
6113 *
6308 *
61146309 */
61156310 this.registerEvent( 'core', 'filterChanged' );
61166311
61176312 /**
61186313 * @ngdoc function
61196314 * @name setRowInvisible
6120 * @methodOf ui.grid.core.api:PublicApi
6315 * @methodOf ui.grid.api:PublicApi
61216316 * @description Sets an override on the row to make it always invisible,
6122 * which will override any filtering or other visibility calculations.
6317 * which will override any filtering or other visibility calculations.
61236318 * If the row is currently visible then sets it to invisible and calls
61246319 * both grid refresh and emits the rowsVisibleChanged event
6125 * @param {object} rowEntity gridOptions.data[] array instance
6320 * @param {GridRow} row the row we want to make invisible
61266321 */
61276322 this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
6128
6323
61296324 /**
61306325 * @ngdoc function
61316326 * @name clearRowInvisible
6132 * @methodOf ui.grid.core.api:PublicApi
6133 * @description Clears any override on visibility for the row so that it returns to
6134 * using normal filtering and other visibility calculations.
6327 * @methodOf ui.grid.api:PublicApi
6328 * @description Clears any override on visibility for the row so that it returns to
6329 * using normal filtering and other visibility calculations.
61356330 * If the row is currently invisible then sets it to visible and calls
61366331 * both grid refresh and emits the rowsVisibleChanged event
61376332 * TODO: if a filter is active then we can't just set it to visible?
6138 * @param {object} rowEntity gridOptions.data[] array instance
6333 * @param {GridRow} row the row we want to make visible
61396334 */
61406335 this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
6141
6336
61426337 /**
61436338 * @ngdoc function
61446339 * @name getVisibleRows
6145 * @methodOf ui.grid.core.api:PublicApi
6340 * @methodOf ui.grid.api:PublicApi
61466341 * @description Returns all visible rows
61476342 * @param {Grid} grid the grid you want to get visible rows from
61486343 * @returns {array} an array of gridRow
61496344 */
61506345 this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
6151
6346
61526347 /**
61536348 * @ngdoc event
61546349 * @name rowsVisibleChanged
6155 * @eventOf ui.grid.core.api:PublicApi
6350 * @eventOf ui.grid.api:PublicApi
61566351 * @description is raised after the rows that are visible
61576352 * change. The filtering is zero-based, so it isn't possible
61586353 * to say which rows changed (unlike in the selection feature).
61676362 /**
61686363 * @ngdoc event
61696364 * @name rowsRendered
6170 * @eventOf ui.grid.core.api:PublicApi
6365 * @eventOf ui.grid.api:PublicApi
61716366 * @description is raised after the cache of visible rows is changed.
61726367 */
61736368 this.registerEvent( 'core', 'rowsRendered' );
61766371 /**
61776372 * @ngdoc event
61786373 * @name scrollBegin
6179 * @eventOf ui.grid.core.api:PublicApi
6374 * @eventOf ui.grid.api:PublicApi
61806375 * @description is raised when scroll begins. Is throttled, so won't be raised too frequently
61816376 */
61826377 this.registerEvent( 'core', 'scrollBegin' );
61846379 /**
61856380 * @ngdoc event
61866381 * @name scrollEnd
6187 * @eventOf ui.grid.core.api:PublicApi
6382 * @eventOf ui.grid.api:PublicApi
61886383 * @description is raised when scroll has finished. Is throttled, so won't be raised too frequently
61896384 */
61906385 this.registerEvent( 'core', 'scrollEnd' );
61926387 /**
61936388 * @ngdoc event
61946389 * @name canvasHeightChanged
6195 * @eventOf ui.grid.core.api:PublicApi
6390 * @eventOf ui.grid.api:PublicApi
61966391 * @description is raised when the canvas height has changed
61976392 * <br/>
61986393 * arguments: oldHeight, newHeight
61996394 */
62006395 this.registerEvent( 'core', 'canvasHeightChanged');
6396
6397 /**
6398 * @ngdoc event
6399 * @name gridDimensionChanged
6400 * @eventOf ui.grid.api:PublicApi
6401 * @description is raised when the grid dimensions have changed (when autoResize is on)
6402 * <br/>
6403 * arguments: oldGridHeight, oldGridWidth, newGridHeight, newGridWidth
6404 */
6405 this.registerEvent( 'core', 'gridDimensionChanged');
62016406 };
62026407
62036408 /**
62126417 * @param {object} callBackFn function to execute
62136418 * @example
62146419 * <pre>
6215 * var navigate = function (newRowCol, oldRowCol){
6420 * var navigate = function (newRowCol, oldRowCol) {
62166421 * //do something on navigate
62176422 * }
62186423 *
62216426 *
62226427 * //call the scrollTo event and suppress our navigate listener
62236428 * //scrollTo will still raise the event for other listeners
6224 * gridApi.suppressEvents(navigate, function(){
6429 * gridApi.suppressEvents(navigate, function() {
62256430 * gridApi.cellNav.scrollTo(aRow, aCol);
62266431 * });
62276432 *
62286433 * </pre>
62296434 */
62306435 GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
6231 var self = this;
6232 var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6233
6234 //find all registered listeners
6436 var self = this,
6437 listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6438
6439 // find all registered listeners
62356440 var foundListeners = self.listeners.filter(function(listener) {
62366441 return listeners.some(function(l) {
62376442 return listener.handler === l;
62386443 });
62396444 });
62406445
6241 //deregister all the listeners
6242 foundListeners.forEach(function(l){
6446 // deregister all the listeners
6447 foundListeners.forEach(function(l) {
62436448 l.dereg();
62446449 });
62456450
62466451 callBackFn();
62476452
6248 //reregister all the listeners
6249 foundListeners.forEach(function(l){
6453 // reregister all the listeners
6454 foundListeners.forEach(function(l) {
62506455 l.dereg = registerEventWithAngular(l.eventId, l.handler, self.grid, l._this);
62516456 });
6252
62536457 };
62546458
62556459 /**
62976501
62986502 // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
62996503 feature.on[eventName] = function (scope, handler, _this) {
6300 if ( scope !== null && typeof(scope.$on) === 'undefined' ){
6504 if ( scope !== null && typeof(scope.$on) === 'undefined' ) {
63016505 gridUtil.logError('asked to listen on ' + featureName + '.on.' + eventName + ' but scope wasn\'t passed in the input parameters. It is legitimate to pass null, but you\'ve passed something else, so you probably forgot to provide scope rather than did it deliberately, not registering');
63026506 return;
63036507 }
63046508 var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
63056509
6306 //track our listener so we can turn off and on
6307 var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
6510 // track our listener so we can turn off and on
6511 var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this: _this};
6512
63086513 self.listeners.push(listener);
63096514
6310 var removeListener = function(){
6515 var removeListener = function() {
63116516 listener.dereg();
63126517 var index = self.listeners.indexOf(listener);
63136518 self.listeners.splice(index,1);
63146519 };
63156520
6316 //destroy tracking when scope is destroyed
6521 // destroy tracking when scope is destroyed
63176522 if (scope) {
63186523 scope.$on('$destroy', function() {
63196524 removeListener();
63206525 });
63216526 }
63226527
6323
63246528 return removeListener;
63256529 };
63266530 };
63286532 function registerEventWithAngular(eventId, handler, grid, _this) {
63296533 return $rootScope.$on(eventId, function (event) {
63306534 var args = Array.prototype.slice.call(arguments);
6331 args.splice(0, 1); //remove evt argument
6535 args.splice(0, 1); // remove evt argument
63326536 handler.apply(_this ? _this : grid.api, args);
63336537 });
63346538 }
63426546 * <pre>
63436547 * {featureName:
63446548 * {
6345 * eventNameOne:function(args){},
6346 * eventNameTwo:function(args){}
6549 * eventNameOne:function(args) {},
6550 * eventNameTwo:function(args) {}
63476551 * }
63486552 * }
63496553 * </pre>
63506554 * @param {object} eventObjectMap map of feature/event names
63516555 */
63526556 GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
6353 var self = this;
6354 var features = [];
6557 var self = this,
6558 features = [];
6559
63556560 angular.forEach(eventObjectMap, function (featProp, featPropName) {
63566561 var feature = {name: featPropName, events: []};
63576562 angular.forEach(featProp, function (prop, propName) {
63656570 self.registerEvent(feature.name, event);
63666571 });
63676572 });
6368
63696573 };
63706574
63716575 /**
63976601 * <br>
63986602 * {featureName:
63996603 * {
6400 * methodNameOne:function(args){},
6401 * methodNameTwo:function(args){}
6604 * methodNameOne:function(args) {},
6605 * methodNameTwo:function(args) {}
64026606 * }
6403 * @param {object} eventObjectMap map of feature/event names
6607 * @param {object} methodMap map of feature/event names
64046608 * @param {object} _this binds this to _this for all functions. Defaults to gridApi.grid
64056609 */
64066610 GridApi.prototype.registerMethodsFromObject = function (methodMap, _this) {
64216625 });
64226626
64236627 };
6424
6628
64256629 return GridApi;
6426
64276630 }]);
6428
64296631 })();
64306632
6431 (function(){
6633 (function() {
64326634
64336635 angular.module('ui.grid')
64346636 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
64486650 * @ngdoc property
64496651 * @name name
64506652 * @propertyOf ui.grid.class:GridColumn
6451 * @description (mandatory) each column should have a name, although for backward
6452 * compatibility with 2.x name can be omitted if field is present
6653 * @description (mandatory) Each column should have a name, although for backward
6654 * compatibility with 2.x name can be omitted if field is present.
6655 *
6656 * Important - This must be unique to each column on a web page since it can
6657 * be used as a key for retrieving information such as custom sort algorithms.
64536658 *
64546659 */
64556660
64576662 * @ngdoc property
64586663 * @name name
64596664 * @propertyOf ui.grid.class:GridOptions.columnDef
6460 * @description (mandatory) each column should have a name, although for backward
6461 * compatibility with 2.x name can be omitted if field is present
6665 * @description (mandatory) Each column should have a name, although for backward
6666 * compatibility with 2.x name can be omitted if field is present.
6667 *
6668 * Important - This must be unique to each column on a web page since it can
6669 * be used as a key for retrieving information such as custom sort algorithms.
64626670 *
64636671 */
64646672
64666674 * @ngdoc property
64676675 * @name displayName
64686676 * @propertyOf ui.grid.class:GridColumn
6469 * @description Column name that will be shown in the header. If displayName is not
6677 * @description Column name that will be shown in the header. If displayName is not
64706678 * provided then one is generated using the name.
64716679 *
64726680 */
64756683 * @ngdoc property
64766684 * @name displayName
64776685 * @propertyOf ui.grid.class:GridOptions.columnDef
6478 * @description Column name that will be shown in the header. If displayName is not
6686 * @description Column name that will be shown in the header. If displayName is not
64796687 * provided then one is generated using the name.
64806688 *
64816689 */
65056713 * @name filter
65066714 * @propertyOf ui.grid.class:GridColumn
65076715 * @description Filter on this column.
6716 *
6717 * Available built-in conditions and types are listed under {@link jui.grid.service:uiGridConstants#properties_filter uiGridOptions.filter}
65086718 * @example
65096719 * <pre>{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...', ariaLabel: 'Filter for text', flags: { caseSensitive: false }, type: uiGridConstants.filter.SELECT, [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ] }</pre>
6720 *
6721 */
6722
6723 /**
6724 * @ngdoc property
6725 * @name extraStyle
6726 * @propertyOf ui.grid.class:GridColumn
6727 * @description additional on this column.
6728 * @example
6729 * <pre>{extraStyle: {display: 'table-cell'}}</pre>
65106730 *
65116731 */
65126732
65276747
65286748 self.updateColumnDef(colDef, true);
65296749
6530 /**
6531 * @ngdoc function
6532 * @name hideColumn
6533 * @methodOf ui.grid.class:GridColumn
6534 * @description Hides the column by setting colDef.visible = false
6535 */
6536 GridColumn.prototype.hideColumn = function() {
6537 this.colDef.visible = false;
6538 };
6539
65406750 self.aggregationValue = undefined;
65416751
65426752 // The footer cell registers to listen for the rowsRendered event, and calls this. Needed to be
65506760 * @name aggregationType
65516761 * @propertyOf ui.grid.class:GridOptions.columnDef
65526762 * @description The aggregation that you'd like to show in the columnFooter for this
6553 * column. Valid values are in uiGridConstants, and currently include `uiGridConstants.aggregationTypes.count`,
6763 * column. Valid values are in
6764 * {@link ui.grid.service:uiGridConstants#properties_aggregationTypes uiGridConstants.aggregationTypes},
6765 * and currently include `uiGridConstants.aggregationTypes.count`,
65546766 * `uiGridConstants.aggregationTypes.sum`, `uiGridConstants.aggregationTypes.avg`, `uiGridConstants.aggregationTypes.min`,
65556767 * `uiGridConstants.aggregationTypes.max`.
65566768 *
65656777 var result = 0;
65666778 var visibleRows = self.grid.getVisibleRows();
65676779
6568 var cellValues = function(){
6780 var cellValues = function() {
65696781 var values = [];
65706782 visibleRows.forEach(function (row) {
65716783 var cellValue = self.grid.getCellValue(row, self);
66256837 };
66266838 }
66276839
6840 /**
6841 * @ngdoc function
6842 * @name hideColumn
6843 * @methodOf ui.grid.class:GridColumn
6844 * @description Hides the column by setting colDef.visible = false
6845 */
6846 GridColumn.prototype.hideColumn = function() {
6847 this.colDef.visible = false;
6848 };
6849
66286850
66296851 /**
66306852 * @ngdoc method
66326854 * @name setPropertyOrDefault
66336855 * @description Sets a property on the column using the passed in columnDef, and
66346856 * setting the defaultValue if the value cannot be found on the colDef
6635 * @param {ColumnDef} colDef the column def to look in for the property value
6857 * @param {GridColumn} colDef the column def to look in for the property value
66366858 * @param {string} propName the property name we'd like to set
66376859 * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
66386860 */
66726894 * @ngdoc property
66736895 * @name minWidth
66746896 * @propertyOf ui.grid.class:GridOptions.columnDef
6675 * @description sets the minimum column width. Should be a number.
6897 * @description Sets the minimum column width. Should be a number.
6898 * Defaults to gridOptions.minimumColumnSize if minWidth is not provided.
66766899 * @example
66776900 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
66786901 *
67086931 * @propertyOf ui.grid.class:GridOptions.columnDef
67096932 * @description An object of sort information, attributes are:
67106933 *
6711 * - direction: values are uiGridConstants.ASC or uiGridConstants.DESC
6934 * - direction: values are {@link ui.grid.service:uiGridConstants#properties_ASC uiGridConstants.ASC}
6935 * or {@link ui.grid.service:uiGridConstants#properties_DESC uiGridConstants.DESC}
67126936 * - ignoreSort: if set to true this sort is ignored (used by tree to manipulate the sort functionality)
6713 * - priority: says what order to sort the columns in (lower priority gets sorted first).
6937 * - priority: says what order to sort the columns in (lower priority gets sorted first). Starts from 0.
67146938 * @example
67156939 * <pre>
67166940 * $scope.gridOptions.columnDefs = [{
67346958 * that are the row objects and the current direction of the sort respectively.
67356959 *
67366960 */
6961
6962 /**
6963 * @ngdoc property
6964 * @name defaultSort
6965 * @propertyOf ui.grid.class:GridOptions.columnDef
6966 * @description An object of sort information, provides a hidden default ordering of the data
6967 * when no user sorts are applied, or when a user-provided sort deems two rows to be equal.
6968 *
6969 * May be combined with a regular {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort}
6970 * to explicitly sort by that column by default.
6971 *
6972 * Shares the same object format as {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort}.
6973 *
6974 * Note that a defaultSort can never take priority over an explicit sort.
6975 * @example
6976 * <pre>
6977 * $scope.gridOptions.columnDefs = [{
6978 * field: 'field1',
6979 * defaultSort: {
6980 * direction: uiGridConstants.ASC,
6981 * priority: 0
6982 * }
6983 * }];
6984 * </pre>
6985 */
67376986
67386987 /**
67396988 * @ngdoc array
68277076 *
68287077 */
68297078
7079 // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
7080 function getDisplayName(colDef) {
7081 return (colDef.displayName === undefined)
7082 ? gridUtil.readableColumnName(colDef.name)
7083 : colDef.displayName;
7084 }
7085
68307086 /**
68317087 * @ngdoc method
68327088 * @methodOf ui.grid.class:GridColumn
68337089 * @name updateColumnDef
68347090 * @description Moves settings from the columnDef down onto the column,
68357091 * and sets properties as appropriate
6836 * @param {ColumnDef} colDef the column def to look in for the property value
7092 * @param {GridColumn} colDef the column def to look in for the property value
68377093 * @param {boolean} isNew whether the column is being newly created, if not
68387094 * we're updating an existing column, and some items such as the sort shouldn't
68397095 * be copied down
68477103 throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
68487104 }
68497105
6850 self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
7106 self.displayName = getDisplayName(colDef);
68517107
68527108 if (!angular.isNumber(self.width) || !self.hasCustomWidth || colDef.allowCustomWidthOverride) {
68537109 var colDefWidth = colDef.width;
68867142 }
68877143 }
68887144
7145 function isValidWidthValue(value) {
7146 return angular.isString(value) || angular.isNumber(value);
7147 }
7148
68897149 ['minWidth', 'maxWidth'].forEach(function (name) {
68907150 var minOrMaxWidth = colDef[name];
68917151 var parseErrorMsg = "Cannot parse column " + name + " '" + minOrMaxWidth + "' for column named '" + colDef.name + "'";
68927152
6893 if (!angular.isString(minOrMaxWidth) && !angular.isNumber(minOrMaxWidth)) {
6894 //Sets default minWidth and maxWidth values
7153 // default minWidth to the minimumColumnSize
7154 if (name === 'minWidth' && !isValidWidthValue(minOrMaxWidth) && angular.isDefined(self.grid.options.minimumColumnSize)) {
7155 minOrMaxWidth = self.grid.options.minimumColumnSize;
7156 }
7157
7158 if (!isValidWidthValue(minOrMaxWidth)) {
7159 // Sets default minWidth and maxWidth values
68957160 self[name] = ((name === 'minWidth') ? 30 : 9000);
68967161 } else if (angular.isString(minOrMaxWidth)) {
68977162 if (minOrMaxWidth.match(/^(\d+)$/)) {
69047169 }
69057170 });
69067171
6907 //use field if it is defined; name if it is not
7172 // use field if it is defined; name if it is not
69087173 self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
69097174
6910 if ( typeof( self.field ) !== 'string' ){
7175 if ( typeof( self.field ) !== 'string' ) {
69117176 gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
69127177 }
69137178
69147179 self.name = colDef.name;
6915
6916 // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
6917 self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
6918
6919 //self.originalIndex = index;
7180 self.displayName = getDisplayName(colDef);
7181
7182 // self.originalIndex = index;
69207183
69217184 self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
69227185 self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
69367199 */
69377200 if ( typeof(colDef.cellTooltip) === 'undefined' || colDef.cellTooltip === false ) {
69387201 self.cellTooltip = false;
6939 } else if ( colDef.cellTooltip === true ){
7202 } else if ( colDef.cellTooltip === true ) {
69407203 self.cellTooltip = function(row, col) {
69417204 return self.grid.getCellValue( row, col );
69427205 };
6943 } else if (typeof(colDef.cellTooltip) === 'function' ){
7206 } else if (typeof(colDef.cellTooltip) === 'function' ) {
69447207 self.cellTooltip = colDef.cellTooltip;
69457208 } else {
6946 self.cellTooltip = function ( row, col ){
7209 self.cellTooltip = function ( row, col ) {
69477210 return col.colDef.cellTooltip;
69487211 };
69497212 }
69637226 */
69647227 if ( typeof(colDef.headerTooltip) === 'undefined' || colDef.headerTooltip === false ) {
69657228 self.headerTooltip = false;
6966 } else if ( colDef.headerTooltip === true ){
7229 }
7230 else if ( colDef.headerTooltip === true ) {
69677231 self.headerTooltip = function(col) {
69687232 return col.displayName;
69697233 };
6970 } else if (typeof(colDef.headerTooltip) === 'function' ){
7234 }
7235 else if (typeof(colDef.headerTooltip) === 'function' ) {
69717236 self.headerTooltip = colDef.headerTooltip;
6972 } else {
7237 }
7238 else {
69737239 self.headerTooltip = function ( col ) {
69747240 return col.colDef.headerTooltip;
69757241 };
69817247 * @name footerCellClass
69827248 * @propertyOf ui.grid.class:GridOptions.columnDef
69837249 * @description footerCellClass can be a string specifying the class to append to a cell
6984 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
7250 * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
69857251 *
69867252 */
69877253 self.footerCellClass = colDef.footerCellClass;
69917257 * @name cellClass
69927258 * @propertyOf ui.grid.class:GridOptions.columnDef
69937259 * @description cellClass can be a string specifying the class to append to a cell
6994 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
7260 * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
69957261 *
69967262 */
69977263 self.cellClass = colDef.cellClass;
70017267 * @name headerCellClass
70027268 * @propertyOf ui.grid.class:GridOptions.columnDef
70037269 * @description headerCellClass can be a string specifying the class to append to a cell
7004 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
7270 * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
70057271 *
70067272 */
70077273 self.headerCellClass = colDef.headerCellClass;
70677333 self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
70687334
70697335 self.headerClass = colDef.headerClass;
7070 //self.cursor = self.sortable ? 'pointer' : 'default';
7336 // self.cursor = self.sortable ? 'pointer' : 'default';
70717337
70727338 // Turn on sorting by default
7073 self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
7339 self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : self.grid.options.enableSorting;
70747340 self.sortingAlgorithm = colDef.sortingAlgorithm;
70757341
70767342 /**
70777343 * @ngdoc property
70787344 * @name sortDirectionCycle
70797345 * @propertyOf ui.grid.class:GridOptions.columnDef
7080 * @description (optional) An array of sort directions, specifying the order that they
7081 * should cycle through as the user repeatedly clicks on the column heading.
7346 * @description (optional) An array of {@link ui.grid.service:uiGridConstants#properties_ASC sort directions},
7347 * specifying the order that they should cycle through as the user repeatedly clicks on the column heading.
70827348 * The default is `[null, uiGridConstants.ASC, uiGridConstants.DESC]`. Null
70837349 * refers to the unsorted state. This does not affect the initial sort
70847350 * direction; use the {@link ui.grid.class:GridOptions.columnDef#sort sort}
70997365 * @description (optional) False by default. When enabled, this setting hides the removeSort option
71007366 * in the menu, and prevents users from manually removing the sort
71017367 */
7102 if ( typeof(self.suppressRemoveSort) === 'undefined'){
7368 if ( typeof(self.suppressRemoveSort) === 'undefined') {
71037369 self.suppressRemoveSort = typeof(colDef.suppressRemoveSort) !== 'undefined' ? colDef.suppressRemoveSort : false;
71047370 }
71057371
71217387 self.setPropertyOrDefault(colDef, 'menuItems', []);
71227388
71237389 // Use the column definition sort if we were passed it, but only if this is a newly added column
7124 if ( isNew ){
7390 if ( isNew ) {
71257391 self.setPropertyOrDefault(colDef, 'sort');
71267392 }
7393
7394 // Use the column definition defaultSort always, unlike normal sort
7395 self.setPropertyOrDefault(colDef, 'defaultSort');
71277396
71287397 // Set up default filters array for when one is not provided.
71297398 // In other words, this (in column def):
71387407 if (colDef.filter) {
71397408 defaultFilters.push(colDef.filter);
71407409 }
7141 else if ( colDef.filters ){
7410 else if ( colDef.filters ) {
71427411 defaultFilters = colDef.filters;
7143 } else {
7412 }
7413 else {
71447414 // Add an empty filter definition object, which will
71457415 // translate to a guessed condition and no pre-populated
71467416 // value for the filter <input>.
71567426 * A filter consists of a condition, a term, and a placeholder:
71577427 *
71587428 * - condition defines how rows are chosen as matching the filter term. This can be set to
7159 * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
7429 * one of the constants in {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter},
7430 * or you can supply a custom filter function
71607431 * that gets passed the following arguments: [searchTerm, cellValue, row, column].
71617432 * - term: If set, the filter field will be pre-populated
71627433 * with this value.
71647435 * - ariaLabel: String that will be set to the `<input>.ariaLabel` attribute. This is what is read as a label to screen reader users.
71657436 * - noTerm: set this to true if you have defined a custom function in condition, and
71667437 * your custom function doesn't require a term (so it can run even when the term is null)
7438 * - rawTerm: set this to true if you have defined a custom function in condition, and
7439 * your custom function requires access to the raw unmodified search term that was entered
71677440 * - flags: only flag currently available is `caseSensitive`, set to false if you don't want
71687441 * case sensitive matching
7169 * - type: defaults to uiGridConstants.filter.INPUT, which gives a text box. If set to uiGridConstants.filter.SELECT
7442 * - type: defaults to {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter.INPUT},
7443 * which gives a text box. If set to {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter.SELECT}
71707444 * then a select box will be shown with options selectOptions
71717445 * - selectOptions: options in the format `[ { value: 1, label: 'male' }]`. No i18n filter is provided, you need
71727446 * to perform the i18n on the values before you provide them
71917465 *
71927466 */
71937467
7194 /*
7195
7196
7197 /*
7198
7199 self.filters = [
7200 {
7201 term: 'search term'
7202 condition: uiGridConstants.filter.CONTAINS,
7203 placeholder: 'my placeholder',
7204 ariaLabel: 'Starts with filter for field1',
7205 flags: {
7206 caseSensitive: true
7207 }
7208 }
7209 ]
7210
7211 */
7212
72137468 // Only set filter if this is a newly added column, if we're updating an existing
72147469 // column then we don't want to put the default filter back if the user may have already
72157470 // removed it.
72167471 // However, we do want to keep the settings if they change, just not the term
72177472 if ( isNew ) {
72187473 self.setPropertyOrDefault(colDef, 'filter');
7474 self.setPropertyOrDefault(colDef, 'extraStyle');
72197475 self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
72207476 } else if ( self.filters.length === defaultFilters.length ) {
7221 self.filters.forEach( function( filter, index ){
7477 self.filters.forEach( function( filter, index ) {
72227478 if (typeof(defaultFilters[index].placeholder) !== 'undefined') {
72237479 filter.placeholder = defaultFilters[index].placeholder;
72247480 }
72367492 }
72377493 });
72387494 }
7239
7240 // Remove this column from the grid sorting, include inside build columns so has
7241 // access to self - all seems a bit dodgy but doesn't work otherwise so have left
7242 // as is
7243 GridColumn.prototype.unsort = function () {
7244 this.sort = {};
7245 self.grid.api.core.raise.sortChanged( self.grid, self.grid.getColumnSorting() );
7246 };
7247
7495 };
7496
7497 /**
7498 * @ngdoc function
7499 * @name unsort
7500 * @methodOf ui.grid.class:GridColumn
7501 * @description Removes column from the grid sorting
7502 */
7503 GridColumn.prototype.unsort = function () {
7504 // Decrease priority for every col where priority is higher than the removed sort's priority.
7505 var thisPriority = this.sort.priority;
7506
7507 this.grid.columns.forEach(function (col) {
7508 if (col.sort && col.sort.priority !== undefined && col.sort.priority > thisPriority) {
7509 col.sort.priority -= 1;
7510 }
7511 });
7512
7513 this.sort = {};
7514 this.grid.api.core.raise.sortChanged( this.grid, this.grid.getColumnSorting() );
72487515 };
72497516
72507517
73447611 */
73457612 GridColumn.prototype.getAggregationText = function () {
73467613 var self = this;
7347 if ( self.colDef.aggregationHideLabel ){
7614 if ( self.colDef.aggregationHideLabel ) {
73487615 return '';
73497616 }
73507617 else if ( self.colDef.aggregationLabel ) {
73517618 return self.colDef.aggregationLabel;
73527619 }
73537620 else {
7354 switch ( self.colDef.aggregationType ){
7621 switch ( self.colDef.aggregationType ) {
73557622 case uiGridConstants.aggregationTypes.count:
73567623 return i18nService.getSafeText('aggregation.count');
73577624 case uiGridConstants.aggregationTypes.sum:
73857652
73867653 })();
73877654
7388 (function(){
7389
7655 (function() {
73907656 angular.module('ui.grid')
7391 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
7657 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil, uiGridConstants) {
73927658
73937659 /**
73947660 * @ngdoc function
74127678 * To provide default options for all of the grids within your application, use an angular
74137679 * decorator to modify the GridOptions factory.
74147680 * <pre>
7415 * app.config(function($provide){
7416 * $provide.decorator('GridOptions',function($delegate){
7681 * app.config(function($provide) {
7682 * $provide.decorator('GridOptions',function($delegate) {
74177683 * var gridOptions;
74187684 * gridOptions = angular.copy($delegate);
74197685 * gridOptions.initialize = function(options) {
74287694 * </pre>
74297695 */
74307696 return {
7431 initialize: function( baseOptions ){
7697 initialize: function( baseOptions ) {
74327698 /**
74337699 * @ngdoc function
74347700 * @name onRegisterApi
75017767 * </br>_field property can be used in place of name for backwards compatibility with 2.x_
75027768 * @example
75037769 *
7504 * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
7770 * <pre>var columnDefs = [{name: 'field1'}, {name: 'field2'}];</pre>
75057771 *
75067772 */
75077773 baseOptions.columnDefs = baseOptions.columnDefs || [];
75127778 * @description Definition / configuration of an individual column, which would typically be
75137779 * one of many column definitions within the gridOptions.columnDefs array
75147780 * @example
7515 * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
7781 * <pre>{name: 'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
75167782 *
75177783 */
75187784
7785 /**
7786 * @ngdoc object
7787 * @name enableGridMenu
7788 * @propertyOf ui.grid.class:GridOptions
7789 * @description Takes a boolean that adds a settings icon in the top right of the grid, which floats above
7790 * the column header, when true. The menu by default gives access to show/hide columns, but can be
7791 * customised to show additional actions.
7792 *
7793 * See the {@link #!/tutorial/121_grid_menu Grid Menu tutorial} for more detailed information.
7794 */
75197795
75207796 /**
75217797 * @ngdoc array
75227798 * @name excludeProperties
7523 * @propertyOf ui.grid.class:GridOptions
7799 * @propertyOf ui.grid.class:GridOptions
75247800 * @description Array of property names in data to ignore when auto-generating column names. Provides the
75257801 * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
75267802 * to exclude.
75937869 */
75947870 baseOptions.showHeader = typeof(baseOptions.showHeader) !== "undefined" ? baseOptions.showHeader : true;
75957871
7596 /* (NOTE): Don't show this in the docs. We only use it internally
7872 /**
75977873 * @ngdoc property
75987874 * @name headerRowHeight
75997875 * @propertyOf ui.grid.class:GridOptions
7600 * @description The height of the header in pixels, defaults to 30
7876 * @description The height of the header in pixels, defaults to 30.
7877 * Although, we recommend that you alter header height with CSS rather than using this option:
76017878 *
7602 */
7879 * <pre>
7880 * .grid .ui-grid-header-cell {
7881 * height: 60px;
7882 * }
7883 * </pre>
7884 **/
76037885 if (!baseOptions.showHeader) {
76047886 baseOptions.headerRowHeight = 0;
76057887 }
76117893 * @ngdoc property
76127894 * @name rowHeight
76137895 * @propertyOf ui.grid.class:GridOptions
7614 * @description The height of the row in pixels, defaults to 30
7896 * @description The height of the row in pixels, Can be passed as integer or string. defaults to 30.
76157897 *
76167898 */
7617 baseOptions.rowHeight = baseOptions.rowHeight || 30;
7899
7900 if (typeof baseOptions.rowHeight === "string") {
7901 baseOptions.rowHeight = parseInt(baseOptions.rowHeight) || 30;
7902 }
7903
7904 else {
7905 baseOptions.rowHeight = baseOptions.rowHeight || 30;
7906 }
76187907
76197908 /**
76207909 * @ngdoc integer
76877976 * Defaults to 4
76887977 */
76897978 baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
7979
76907980 /**
76917981 * @ngdoc property
76927982 * @name scrollThreshold
76937983 * @propertyOf ui.grid.class:GridOptions
7694 * @description Defaults to 4
7984 * @description Throttles the grid scrolling by the amount of rows set, which helps with smoothness of scrolling.
7985 * Defaults to 4.
76957986 */
76967987 baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
76977988
77037994 * Defaults to 4
77047995 */
77057996 baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
7706 /**
7707 * @ngdoc property
7708 * @name horizontalScrollThreshold
7709 * @propertyOf ui.grid.class:GridOptions
7710 * @description Defaults to 4
7711 */
7712 baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
7713
77147997
77157998 /**
77167999 * @ngdoc property
77438026 * @propertyOf ui.grid.class:GridOptions
77448027 * @description True by default. When enabled, this setting adds sort
77458028 * widgets to the column headers, allowing sorting of the data for the entire grid.
7746 * Sorting can then be disabled on individual columns using the columnDefs.
8029 * Sorting can then be disabled / enabled on individual columns using the columnDefs,
8030 * if it set, it will override GridOptions enableSorting setting.
77478031 */
77488032 baseOptions.enableSorting = baseOptions.enableSorting !== false;
77498033
77638047 * @propertyOf ui.grid.class:GridOptions
77648048 * @description True by default. When enabled, this setting displays a column
77658049 * menu within each column.
8050 * By default column menu's trigger is hidden before mouse over, but you can always force it to be visible with CSS:
8051 *
8052 * <pre>
8053 * .ui-grid-column-menu-button {
8054 * display: block;
8055 * }
8056 * </pre>
77668057 */
77678058 baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
77688059
77708061 * @ngdoc boolean
77718062 * @name enableVerticalScrollbar
77728063 * @propertyOf ui.grid.class:GridOptions
7773 * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the vertical scrollbar for the grid.
7774 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
8064 * @description {@link ui.grid.service:uiGridConstants#properties_scrollbars uiGridConstants.scrollbars.ALWAYS} by default.
8065 * This settings controls the vertical scrollbar for the grid.
8066 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER, uiGridConstants.scrollbars.WHEN_NEEDED
77758067 */
77768068 baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
77778069
77798071 * @ngdoc boolean
77808072 * @name enableHorizontalScrollbar
77818073 * @propertyOf ui.grid.class:GridOptions
7782 * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the horizontal scrollbar for the grid.
7783 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
8074 * @description {@link ui.grid.service:uiGridConstants#properties_scrollbars uiGridConstants.scrollbars.ALWAYS} by default.
8075 * This settings controls the horizontal scrollbar for the grid.
8076 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER, uiGridConstants.scrollbars.WHEN_NEEDED
77848077 */
77858078 baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
77868079
77988091 * @ngdoc boolean
77998092 * @name minimumColumnSize
78008093 * @propertyOf ui.grid.class:GridOptions
7801 * @description Columns can't be smaller than this, defaults to 10 pixels
8094 * @description Sets the default minimum column width, in other words,
8095 * it defines the default value for a column minWidth attribute if that is not otherwise specified.
8096 * Should be a number. Defaults to 30 pixels.
78028097 */
7803 baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
8098 baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 30;
78048099
78058100 /**
78068101 * @ngdoc function
78728167 baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
78738168
78748169 /**
8170 * @ngdoc string
8171 * @name gridMenuTemplate
8172 * @propertyOf ui.grid.class:GridOptions
8173 * @description 'ui-grid/uiGridMenu' by default. When provided, this setting uses a
8174 * custom grid menu template.
8175 */
8176 baseOptions.gridMenuTemplate = baseOptions.gridMenuTemplate || 'ui-grid/uiGridMenu';
8177
8178 /**
78758179 * @ngdoc object
78768180 * @name appScopeProvider
78778181 * @propertyOf ui.grid.class:GridOptions
78838187 return baseOptions;
78848188 }
78858189 };
7886
7887
78888190 }]);
7889
78908191 })();
78918192
7892 (function(){
8193 (function() {
78938194
78948195 angular.module('ui.grid')
78958196
79328233 self.prevScrollleftPercentage = 0;
79338234 self.prevColumnScrollIndex = 0;
79348235
7935 self.columnStyles = "";
8236 self.columnStyles = '';
79368237
79378238 self.viewportAdjusters = [];
79388239
80188319
80198320 var min = 0;
80208321 var totalWidth = 0;
8021 // self.columns.forEach(function(col, i) {
8322
80228323 for (var i = 0; i < self.visibleColumnCache.length; i++) {
80238324 var col = self.visibleColumnCache[i];
80248325
81288429
81298430 var viewportWidth = self.grid.gridWidth;
81308431
8131 //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8132 // viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8133 //}
8432 // if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined &&
8433 // self.grid.verticalScrollbarWidth > 0) {
8434 // viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8435 // }
81348436
81358437 // var viewportWidth = 0;\
81368438 // self.visibleColumnCache.forEach(function (column) {
81458447 };
81468448
81478449 GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
8148 var self = this;
8149
8150 var viewportWidth = this.getViewportWidth();
8151
8152 //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8153 // viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
8154 //}
8155
8156 // var adjustment = self.getViewportAdjustment();
8157 // viewPortWidth = viewPortWidth + adjustment.width;
8158
8159 return viewportWidth;
8450 return this.getViewportWidth();
81608451 };
81618452
81628453
81768467
81778468 var oldCanvasHeight = self.$$canvasHeight;
81788469
8179 self.$$canvasHeight = 0;
8180
8181 self.visibleRowCache.forEach(function(row){
8470 self.$$canvasHeight = 0;
8471
8472 self.visibleRowCache.forEach(function(row) {
81828473 self.$$canvasHeight += row.height;
81838474 });
81848475
81918482 };
81928483
81938484 GridRenderContainer.prototype.getVerticalScrollLength = function getVerticalScrollLength() {
8194 return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight;
8485 return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight !== 0 ? this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight : -1;
8486 };
8487
8488 GridRenderContainer.prototype.getHorizontalScrollLength = function getHorizontalScrollLength() {
8489 return this.getCanvasWidth() - this.getViewportWidth() + this.grid.scrollbarWidth !== 0 ? this.getCanvasWidth() - this.getViewportWidth() + this.grid.scrollbarWidth : -1;
81958490 };
81968491
81978492 GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
81988493 var self = this;
81998494
8200 var ret = self.canvasWidth;
8201
8202 return ret;
8495 return self.canvasWidth;
82038496 };
82048497
82058498 GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
82108503 };
82118504
82128505 GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
8213 var self = this;
8214
82158506 // OLD:
82168507 this.renderedColumns.length = newColumns.length;
82178508 for (var i = 0; i < newColumns.length; i++) {
82458536
82468537 vertScrollPercentage = newScrollTop / vertScrollLength;
82478538
8248 // console.log('vert', vertScrollPercentage, newScrollTop, vertScrollLength);
8249
82508539 if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
82518540 if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
82528541
82558544 }
82568545 };
82578546
8258 GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft){
8547 GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft) {
82598548 var horizScrollPercentage = -1;
82608549
82618550 // Handle RTL here
8262
82638551 if (newScrollLeft !== this.prevScrollLeft) {
82648552 var xDiff = newScrollLeft - this.prevScrollLeft;
82658553
82668554 if (xDiff > 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; }
82678555 if (xDiff < 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; }
82688556
8269 var horizScrollLength = (this.canvasWidth - this.getViewportWidth());
8557 var horizScrollLength = this.getHorizontalScrollLength();
82708558 if (horizScrollLength !== 0) {
82718559 horizScrollPercentage = newScrollLeft / horizScrollLength;
82728560 }
83228610
83238611 var maxRowIndex = rowCache.length - minRows;
83248612
8325 // console.log('scroll%1', scrollPercentage);
8326
83278613 // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
83288614 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
83298615 scrollPercentage = scrollTop / self.getVerticalScrollLength();
83308616 }
83318617
83328618 var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
8333
8334 // console.log('maxRowIndex / scroll%', maxRowIndex, scrollPercentage, rowIndex);
83358619
83368620 // Define a max row index that we can't scroll past
83378621 if (rowIndex > maxRowIndex) {
83458629 if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
83468630 return;
83478631 }
8348 //Have we hit the threshold going up?
8632 // Have we hit the threshold going up?
83498633 if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
83508634 return;
83518635 }
83528636 }
8353 var rangeStart = {};
8354 var rangeEnd = {};
8355
8356 rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8357 rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
8637
8638 var rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8639 var rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
83588640
83598641 newRange = [rangeStart, rangeEnd];
83608642 }
83788660
83798661 // Calculate the scroll percentage according to the scrollLeft location, if no percentage was provided
83808662 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
8381 var horizScrollLength = (self.getCanvasWidth() - self.getViewportWidth());
8382 scrollPercentage = scrollLeft / horizScrollLength;
8663 scrollPercentage = scrollLeft / self.getHorizontalScrollLength();
83838664 }
83848665
83858666 var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
83918672
83928673 var newRange = [];
83938674 if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
8394 /* Commented the following lines because otherwise the moved column wasn't visible immediately on the new position
8395 * in the case of many columns with horizontal scroll, one had to scroll left or right and then return in order to see it
8396 // Have we hit the threshold going down?
8397 if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8398 return;
8399 }
8400 //Have we hit the threshold going up?
8401 if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8402 return;
8403 }*/
8404
84058675 var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
84068676 var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
84078677
84968766 var asterisksArray = [],
84978767 asteriskNum = 0,
84988768 usedWidthSum = 0,
8499 ret = '';
8769 ret = '',
8770 pinRightColumn = false,
8771 fixedNumberArray = [],
8772 percentageArray = [],
8773 totalPercentage = 0;
85008774
85018775 // Get the width of the viewport
85028776 var availableWidth = self.grid.getViewportWidth() - self.grid.scrollbarWidth;
85048778 // get all the columns across all render containers, we have to calculate them all or one render container
85058779 // could consume the whole viewport
85068780 var columnCache = [];
8507 angular.forEach(self.grid.renderContainers, function( container, name){
8781 angular.forEach(self.grid.renderContainers, function (container) {
85088782 columnCache = columnCache.concat(container.visibleColumnCache);
85098783 });
85108784
85118785 // look at each column, process any manual values or %, put the * into an array to look at later
8512 columnCache.forEach(function(column, i) {
8786 columnCache.forEach(function (column) {
85138787 var width = 0;
85148788 // Skip hidden columns
85158789 if (!column.visible) { return; }
8790
8791 if (pinRightColumn) {
8792 availableWidth += self.grid.scrollbarWidth;
8793 }
8794
8795 if (!pinRightColumn && column.colDef.pinnedRight) {
8796 pinRightColumn = true;
8797 }
85168798
85178799 if (angular.isNumber(column.width)) {
85188800 // pixel width, set to this value
85208802 usedWidthSum = usedWidthSum + width;
85218803 column.drawnWidth = width;
85228804
8523 } else if (gridUtil.endsWith(column.width, "%")) {
8805 fixedNumberArray.push(column);
8806 } else if (gridUtil.endsWith(column.width, '%')) {
85248807 // percentage width, set to percentage of the viewport
8525 width = parseInt(parseInt(column.width.replace(/%/g, ''), 10) / 100 * availableWidth);
8526
8527 if ( width > column.maxWidth ){
8808 // round down to int - some browsers don't play nice with float maxWidth
8809 var percentageIntegerValue = parseInt(column.width.replace(/%/g, ''), 10);
8810 width = parseInt(percentageIntegerValue / 100 * availableWidth);
8811
8812 if (width > column.maxWidth) {
85288813 width = column.maxWidth;
85298814 }
85308815
8531 if ( width < column.minWidth ){
8816 if (width < column.minWidth) {
85328817 width = column.minWidth;
85338818 }
85348819
85358820 usedWidthSum = usedWidthSum + width;
85368821 column.drawnWidth = width;
8822
8823 totalPercentage = totalPercentage + percentageIntegerValue;
8824 percentageArray.push(column);
85378825 } else if (angular.isString(column.width) && column.width.indexOf('*') !== -1) {
85388826 // is an asterisk column, the gridColumn already checked the string consists only of '****'
85398827 asteriskNum = asteriskNum + column.width.length;
85448832 // Get the remaining width (available width subtracted by the used widths sum)
85458833 var remainingWidth = availableWidth - usedWidthSum;
85468834
8547 var i, column, colWidth;
8548
85498835 if (asterisksArray.length > 0) {
85508836 // the width that each asterisk value would be assigned (this can be negative)
85518837 var asteriskVal = remainingWidth / asteriskNum;
85528838
8553 asterisksArray.forEach(function( column ){
8839 asterisksArray.forEach(function (column) {
85548840 var width = parseInt(column.width.length * asteriskVal, 10);
85558841
8556 if ( width > column.maxWidth ){
8557 width = column.maxWidth;
8558 }
8559
8560 if ( width < column.minWidth ){
8561 width = column.minWidth;
8842 if (width > column.maxWidth) {
8843 width = column.maxWidth;
8844 }
8845
8846 if (width < column.minWidth) {
8847 width = column.minWidth;
85628848 }
85638849
85648850 usedWidthSum = usedWidthSum + width;
85668852 });
85678853 }
85688854
8569 // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8570 // calculated widths would have the grid narrower than the available space,
8571 // dole the remainder out one by one to make everything fit
8572 var processColumnUpwards = function(column){
8573 if ( column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8574 column.drawnWidth++;
8575 usedWidthSum++;
8576 leftoverWidth--;
8577 columnsToChange = true;
8855 // If there are no columns with asterisk widths then check if there are any with % widths and
8856 // use them as a fallback for adjusting column widths up or down if we have remaining grid width
8857 // or need to claw some width back
8858 var variableWidthColumnArray;
8859 if (asterisksArray.length > 0) {
8860 variableWidthColumnArray = asterisksArray;
8861 } else if (percentageArray.length > 0 && fixedNumberArray.length === 0 && totalPercentage === 100) {
8862 variableWidthColumnArray = percentageArray;
8863 }
8864
8865 if (!angular.isUndefined(variableWidthColumnArray)) {
8866 // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8867 // calculated widths would have the grid narrower than the available space,
8868 // dole the remainder out one by one to make everything fit
8869 var processColumnUpwards = function (column) {
8870 if (column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8871 column.drawnWidth++;
8872 usedWidthSum++;
8873 leftoverWidth--;
8874 columnsToChange = true;
8875 }
8876 };
8877
8878 var leftoverWidth = availableWidth - usedWidthSum;
8879 var columnsToChange = true;
8880
8881 while (leftoverWidth > 0 && columnsToChange) {
8882 columnsToChange = false;
8883 variableWidthColumnArray.forEach(processColumnUpwards);
85788884 }
8579 };
8580
8581 var leftoverWidth = availableWidth - usedWidthSum;
8582 var columnsToChange = true;
8583
8584 while (leftoverWidth > 0 && columnsToChange) {
8585 columnsToChange = false;
8586 asterisksArray.forEach(processColumnUpwards);
8885
8886 // We can end up with too much width even though some columns aren't at their max width, in this situation
8887 // we can trim the columns a little
8888 var processColumnDownwards = function (column) {
8889 if (column.drawnWidth > column.minWidth && excessWidth > 0) {
8890 column.drawnWidth--;
8891 usedWidthSum--;
8892 excessWidth--;
8893 columnsToChange = true;
8894 }
8895 };
8896
8897 var excessWidth = usedWidthSum - availableWidth;
8898 columnsToChange = true;
8899
8900 while (excessWidth > 0 && columnsToChange) {
8901 columnsToChange = false;
8902 variableWidthColumnArray.forEach(processColumnDownwards);
8903 }
85878904 }
8588
8589 // We can end up with too much width even though some columns aren't at their max width, in this situation
8590 // we can trim the columns a little
8591 var processColumnDownwards = function(column){
8592 if ( column.drawnWidth > column.minWidth && excessWidth > 0) {
8593 column.drawnWidth--;
8594 usedWidthSum--;
8595 excessWidth--;
8596 columnsToChange = true;
8597 }
8598 };
8599
8600 var excessWidth = usedWidthSum - availableWidth;
8601 columnsToChange = true;
8602
8603 while (excessWidth > 0 && columnsToChange) {
8604 columnsToChange = false;
8605 asterisksArray.forEach(processColumnDownwards);
8606 }
8607
86088905
86098906 // all that was across all the renderContainers, now we need to work out what that calculation decided for
86108907 // our renderContainer
86118908 var canvasWidth = 0;
8612 self.visibleColumnCache.forEach(function(column){
8613 if ( column.visible ){
8909 self.visibleColumnCache.forEach(function(column) {
8910 if ( column.visible ) {
86148911 canvasWidth = canvasWidth + column.drawnWidth;
86158912 }
86168913 });
86308927 };
86318928
86328929 GridRenderContainer.prototype.needsHScrollbarPlaceholder = function () {
8633 return this.grid.options.enableHorizontalScrollbar && !this.hasHScrollbar && !this.grid.disableScrolling;
8930 var self = this,
8931 containerBody;
8932
8933 if (self.name === 'left' || self.name === 'right' && !this.hasHScrollbar && !this.grid.disableScrolling) {
8934 if (self.grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
8935 return true;
8936 }
8937 containerBody = this.grid.element[0].querySelector('.ui-grid-render-container-body .ui-grid-viewport');
8938 return containerBody.scrollWidth > containerBody.offsetWidth;
8939 }
8940 return false;
86348941 };
86358942
86368943 GridRenderContainer.prototype.getViewportStyle = function () {
86378944 var self = this;
86388945 var styles = {};
8946 var scrollbarVisibility = {};
8947
8948 scrollbarVisibility[uiGridConstants.scrollbars.ALWAYS] = 'scroll';
8949 scrollbarVisibility[uiGridConstants.scrollbars.WHEN_NEEDED] = 'auto';
86398950
86408951 self.hasHScrollbar = false;
86418952 self.hasVScrollbar = false;
86668977 self.hasVScrollbar = !self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
86678978 }
86688979
8669 styles['overflow-x'] = self.hasHScrollbar ? 'scroll' : 'hidden';
8670 styles['overflow-y'] = self.hasVScrollbar ? 'scroll' : 'hidden';
8671
8980 styles['overflow-x'] = self.hasHScrollbar ? scrollbarVisibility[self.grid.options.enableHorizontalScrollbar] : 'hidden';
8981 styles['overflow-y'] = self.hasVScrollbar ? scrollbarVisibility[self.grid.options.enableVerticalScrollbar] : 'hidden';
86728982
86738983 return styles;
8674
8675
86768984 };
86778985
86788986 return GridRenderContainer;
86808988
86818989 })();
86828990
8683 (function(){
8991 (function() {
86848992
86858993 angular.module('ui.grid')
8686 .factory('GridRow', ['gridUtil', function(gridUtil) {
8994 .factory('GridRow', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
86878995
86888996 /**
8997 * @class GridRow
86898998 * @ngdoc function
86908999 * @name ui.grid.class:GridRow
86919000 * @description GridRow is the viewModel for one logical row on the grid. A grid Row is not necessarily a one-to-one
86929001 * relation to gridOptions.data.
86939002 * @param {object} entity the array item from GridOptions.data
86949003 * @param {number} index the current position of the row in the array
8695 * @param {Grid} reference to the parent grid
9004 * @param {Grid} grid reference to the parent grid
86969005 */
86979006 function GridRow(entity, index, grid) {
86989007
87299038 // Default to true
87309039 this.visible = true;
87319040
9041 /**
9042 * @ngdoc object
9043 * @name isSelected
9044 * @propertyOf ui.grid.class:GridRow
9045 * @description Marks if the row has been selected
9046 */
9047 // Default to false
9048 this.isSelected = false;
9049
87329050
87339051 this.$$height = grid.options.rowHeight;
87349052
87379055 /**
87389056 * @ngdoc object
87399057 * @name height
8740 * @propertyOf ui.grid.class:GridRow
9058 * @propertyOf ui.grid.class:GridRow
87419059 * @description height of each individual row. changing the height will flag all
87429060 * row renderContainers to recalculate their canvas height
87439061 */
87599077 * @methodOf ui.grid.class:GridRow
87609078 * @description returns the qualified field name as it exists on scope
87619079 * ie: row.entity.fieldA
8762 * @param {GridCol} col column instance
9080 * @param {GridColumn} col column instance
87639081 * @returns {string} resulting name that can be evaluated on scope
87649082 */
87659083 GridRow.prototype.getQualifiedColField = function(col) {
87729090 * @methodOf ui.grid.class:GridRow
87739091 * @description returns the qualified field name minus the row path
87749092 * ie: entity.fieldA
8775 * @param {GridCol} col column instance
9093 * @param {GridColumn} col column instance
87769094 * @returns {string} resulting name that can be evaluated against a row
87779095 */
87789096 GridRow.prototype.getEntityQualifiedColField = function(col) {
8779 return gridUtil.preEval('entity.' + col.field);
9097 var base = 'entity';
9098 if ( col.field === uiGridConstants.ENTITY_BINDING ) {
9099 return base;
9100 }
9101 return gridUtil.preEval(base + '.' + col.field);
87809102 };
8781
8782
9103
9104
87839105 /**
87849106 * @ngdoc function
87859107 * @name setRowInvisible
87869108 * @methodOf ui.grid.class:GridRow
87879109 * @description Sets an override on the row that forces it to always
87889110 * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8789 *
9111 *
87909112 * This method can be called from the api, passing in the gridRow we want
87919113 * altered. It should really work by calling gridRow.setRowInvisible, but that's
87929114 * not the way I coded it, and too late to change now. Changed to just call
87939115 * the internal function row.setThisRowInvisible().
8794 *
9116 *
87959117 * @param {GridRow} row the row we want to set to invisible
8796 *
9118 *
87979119 */
87989120 GridRow.prototype.setRowInvisible = function ( row ) {
8799 if (row && row.setThisRowInvisible){
9121 if (row && row.setThisRowInvisible) {
88009122 row.setThisRowInvisible( 'user' );
88019123 }
88029124 };
8803
8804
9125
9126
88059127 /**
88069128 * @ngdoc function
88079129 * @name clearRowInvisible
88089130 * @methodOf ui.grid.class:GridRow
88099131 * @description Clears an override on the row that forces it to always
88109132 * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
8811 *
9133 *
88129134 * This method can be called from the api, passing in the gridRow we want
88139135 * altered. It should really work by calling gridRow.clearRowInvisible, but that's
88149136 * not the way I coded it, and too late to change now. Changed to just call
88159137 * the internal function row.clearThisRowInvisible().
8816 *
9138 *
88179139 * @param {GridRow} row the row we want to clear the invisible flag
8818 *
9140 *
88199141 */
88209142 GridRow.prototype.clearRowInvisible = function ( row ) {
8821 if (row && row.clearThisRowInvisible){
9143 if (row && row.clearThisRowInvisible) {
88229144 row.clearThisRowInvisible( 'user' );
88239145 }
88249146 };
8825
8826
9147
9148
88279149 /**
88289150 * @ngdoc function
88299151 * @name setThisRowInvisible
88369158 * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
88379159 */
88389160 GridRow.prototype.setThisRowInvisible = function ( reason, fromRowsProcessor ) {
8839 if ( !this.invisibleReason ){
9161 if ( !this.invisibleReason ) {
88409162 this.invisibleReason = {};
88419163 }
88429164 this.invisibleReason[reason] = true;
88489170 * @ngdoc function
88499171 * @name clearRowInvisible
88509172 * @methodOf ui.grid.class:GridRow
8851 * @description Clears any override on the row visibility, returning it
9173 * @description Clears any override on the row visibility, returning it
88529174 * to normal visibility calculations. Emits the rowsVisibleChanged
88539175 * event
8854 *
9176 *
88559177 * @param {string} reason the reason (usually the module) for the row to be invisible.
88569178 * E.g. grouping, user, filter
88579179 * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
88689190 * @ngdoc function
88699191 * @name evaluateRowVisibility
88709192 * @methodOf ui.grid.class:GridRow
8871 * @description Determines whether the row should be visible based on invisibleReason,
9193 * @description Determines whether the row should be visible based on invisibleReason,
88729194 * and if it changes the row visibility, then emits the rowsVisibleChanged event.
8873 *
9195 *
88749196 * Queues a grid refresh, but doesn't call it directly to avoid hitting lots of grid refreshes.
88759197 * @param {boolean} fromRowProcessor if true, then it won't raise events or queue the refresh, the
88769198 * row processor does that already
88779199 */
88789200 GridRow.prototype.evaluateRowVisibility = function ( fromRowProcessor ) {
88799201 var newVisibility = true;
8880 if ( typeof(this.invisibleReason) !== 'undefined' ){
8881 angular.forEach(this.invisibleReason, function( value, key ){
8882 if ( value ){
9202 if ( typeof(this.invisibleReason) !== 'undefined' ) {
9203 angular.forEach(this.invisibleReason, function( value, key ) {
9204 if ( value ) {
88839205 newVisibility = false;
88849206 }
88859207 });
88869208 }
8887
8888 if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ){
9209
9210 if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ) {
88899211 this.visible = newVisibility;
8890 if ( !fromRowProcessor ){
9212 if ( !fromRowProcessor ) {
88919213 this.grid.queueGridRefresh();
88929214 this.grid.api.core.raise.rowsVisibleChanged(this);
88939215 }
88949216 }
88959217 };
8896
9218
88979219
88989220 return GridRow;
88999221 }]);
89009222
89019223 })();
89029224
8903 (function(){
9225 (function() {
89049226 'use strict';
89059227 /**
89069228 * @ngdoc object
89129234 */
89139235 angular.module('ui.grid')
89149236 .factory('GridRowColumn', ['$parse', '$filter',
8915 function GridRowColumnFactory($parse, $filter){
9237 function GridRowColumnFactory($parse, $filter) {
89169238 var GridRowColumn = function GridRowColumn(row, col) {
8917 if ( !(this instanceof GridRowColumn)){
9239 if ( !(this instanceof GridRowColumn)) {
89189240 throw "Using GridRowColumn as a function insead of as a constructor. Must be called with `new` keyword";
89199241 }
89209242
89429264 * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
89439265 * If the column has a cellFilter this will NOT return the filtered value.
89449266 */
8945 GridRowColumn.prototype.getIntersectionValueRaw = function(){
9267 GridRowColumn.prototype.getIntersectionValueRaw = function() {
89469268 var getter = $parse(this.row.getEntityQualifiedColField(this.col));
89479269 var context = this.row;
89489270 return getter(context);
8949 };
8950 /**
8951 * @ngdoc function
8952 * @name getIntersectionValueFiltered
8953 * @methodOf ui.grid.class:GridRowColumn
8954 * @description Gets the intersection of where the row and column meet.
8955 * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
8956 * If the column has a cellFilter this will also apply the filter to it and return the value that the filter displays.
8957 */
8958 GridRowColumn.prototype.getIntersectionValueFiltered = function(){
8959 var value = this.getIntersectionValueRaw();
8960 if (this.col.cellFilter && this.col.cellFilter !== ''){
8961 var getFilterIfExists = function(filterName){
8962 try {
8963 return $filter(filterName);
8964 } catch (e){
8965 return null;
8966 }
8967 };
8968 var filter = getFilterIfExists(this.col.cellFilter);
8969 if (filter) { // Check if this is filter name or a filter string
8970 value = filter(value);
8971 } else { // We have the template version of a filter so we need to parse it apart
8972 // Get the filter params out using a regex
8973 // Test out this regex here https://regex101.com/r/rC5eR5/2
8974 var re = /([^:]*):([^:]*):?([\s\S]+)?/;
8975 var matches;
8976 if ((matches = re.exec(this.col.cellFilter)) !== null) {
8977 // View your result using the matches-variable.
8978 // eg matches[0] etc.
8979 value = $filter(matches[1])(value, matches[2], matches[3]);
8980 }
8981 }
8982 }
8983 return value;
89849271 };
89859272 return GridRowColumn;
89869273 }
90659352 * @methodOf ui.grid.class:ScrollEvent
90669353 * @description returns newScrollLeft property if available; calculates a new value if it isn't
90679354 */
9068 ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport){
9355 ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport) {
90699356 var self = this;
90709357
9071 if (!self.newScrollLeft){
9358 if (!self.newScrollLeft) {
90729359 var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
90739360
90749361 var oldScrollLeft = gridUtil.normalizeScrollLeft(viewport, self.grid);
90979384 * @methodOf ui.grid.class:ScrollEvent
90989385 * @description returns newScrollTop property if available; calculates a new value if it isn't
90999386 */
9100 ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport){
9387 ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport) {
91019388 var self = this;
91029389
91039390
9104 if (!self.newScrollTop){
9391 if (!self.newScrollTop) {
91059392 var scrollLength = rowContainer.getVerticalScrollLength();
91069393
91079394 var oldScrollTop = viewport[0].scrollTop;
91849471 if (grid.options.rowTemplate) {
91859472 var rowTemplateFnPromise = $q.defer();
91869473 grid.getRowTemplateFn = rowTemplateFnPromise.promise;
9187
9474
91889475 gridUtil.getTemplate(grid.options.rowTemplate)
91899476 .then(
91909477 function (template) {
91919478 var rowTemplateFn = $compile(template);
91929479 rowTemplateFnPromise.resolve(rowTemplateFn);
91939480 },
9194 function (res) {
9481 function () {
91959482 // Todo handle response error here?
91969483 throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
9197 });
9484 }).catch(angular.noop);
91989485 }
91999486
92009487 grid.registerColumnBuilder(service.defaultColumnBuilder);
92069493 grid.registerRowsProcessor(function allRowsVisible(rows) {
92079494 rows.forEach(function (row) {
92089495 row.evaluateRowVisibility( true );
9209 }, 50);
9496 });
92109497
92119498 return rows;
9212 });
9213
9214 grid.registerColumnsProcessor(function allColumnsVisible(columns) {
9499 }, 50);
9500
9501 grid.registerColumnsProcessor(function applyColumnVisibility(columns) {
92159502 columns.forEach(function (column) {
9216 column.visible = true;
9503 column.visible = angular.isDefined(column.colDef.visible) ? column.colDef.visible : true;
92179504 });
92189505
92199506 return columns;
92209507 }, 50);
9221
9222 grid.registerColumnsProcessor(function(renderableColumns) {
9223 renderableColumns.forEach(function (column) {
9224 if (column.colDef.visible === false) {
9225 column.visible = false;
9226 }
9227 });
9228
9229 return renderableColumns;
9230 }, 50);
9231
92329508
92339509 grid.registerRowsProcessor(grid.searchRows, 100);
92349510
92599535
92609536 // Abstracts the standard template processing we do for every template type.
92619537 var processTemplate = function( templateType, providedType, defaultTemplate, filterType, tooltipType ) {
9262 if ( !colDef[templateType] ){
9538 if ( !colDef[templateType] ) {
92639539 col[providedType] = defaultTemplate;
92649540 } else {
92659541 col[providedType] = colDef[templateType];
92669542 }
9267
9268 templateGetPromises.push(gridUtil.getTemplate(col[providedType])
9269 .then(
9543
9544 var templatePromise = gridUtil.getTemplate(col[providedType])
9545 .then(
92709546 function (template) {
92719547 if ( angular.isFunction(template) ) { template = template(); }
9272 var tooltipCall = ( tooltipType === 'cellTooltip' ) ? 'col.cellTooltip(row,col)' : 'col.headerTooltip(col)';
9273 if ( tooltipType && col[tooltipType] === false ){
9548 var tooltipCall = ( tooltipType === 'cellTooltip' )
9549 ? 'col.cellTooltip(row,col)'
9550 : 'col.headerTooltip(col)';
9551 if ( tooltipType && col[tooltipType] === false ) {
92749552 template = template.replace(uiGridConstants.TOOLTIP, '');
9275 } else if ( tooltipType && col[tooltipType] ){
9553 } else if ( tooltipType && col[tooltipType] ) {
92769554 template = template.replace(uiGridConstants.TOOLTIP, 'title="{{' + tooltipCall + ' CUSTOM_FILTERS }}"');
92779555 }
92789556
9279 if ( filterType ){
9557 if ( filterType ) {
92809558 col[templateType] = template.replace(uiGridConstants.CUSTOM_FILTERS, function() {
92819559 return col[filterType] ? "|" + col[filterType] : "";
92829560 });
92849562 col[templateType] = template;
92859563 }
92869564 },
9287 function (res) {
9565 function () {
92889566 throw new Error("Couldn't fetch/use colDef." + templateType + " '" + colDef[templateType] + "'");
9289 })
9290 );
9291
9567 }).catch(angular.noop);
9568
9569 templateGetPromises.push(templatePromise);
9570
9571 return templatePromise;
92929572 };
92939573
92949574
93019581 * must contain a div that can receive focus.
93029582 *
93039583 */
9304 processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
9305 col.cellTemplatePromise = templateGetPromises[0];
9584 col.cellTemplatePromise = processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
93069585
93079586 /**
93089587 * @ngdoc property
93129591 * is ui-grid/uiGridHeaderCell
93139592 *
93149593 */
9315 processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
9594 col.headerCellTemplatePromise = processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
93169595
93179596 /**
93189597 * @ngdoc property
93229601 * is ui-grid/uiGridFooterCell
93239602 *
93249603 */
9325 processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
9604 col.footerCellTemplatePromise = processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
93269605
93279606 /**
93289607 * @ngdoc property
93319610 * @description a custom template for the filter input. The default is ui-grid/ui-grid-filter
93329611 *
93339612 */
9334 processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
9613 col.filterHeaderTemplatePromise = processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
93359614
93369615 // Create a promise for the compiled element function
93379616 col.compiledElementFnDefer = $q.defer();
93389617
93399618 return $q.all(templateGetPromises);
93409619 },
9341
93429620
93439621 rowTemplateAssigner: function rowTemplateAssigner(row) {
93449622 var grid = this;
93629640 .then(function (template) {
93639641 // Compile the template
93649642 var rowTemplateFn = $compile(template);
9365
9643
93669644 // Resolve the compiled template function promise
93679645 perRowTemplateFnPromise.resolve(rowTemplateFn);
93689646 },
9369 function (res) {
9647 function () {
93709648 // Todo handle response error here?
93719649 throw new Error("Couldn't fetch/use row template '" + row.rowTemplate + "'");
93729650 });
93769654 }
93779655 };
93789656
9379 //class definitions (moved to separate factories)
9657 // class definitions (moved to separate factories)
93809658
93819659 return service;
93829660 }]);
94149692 */
94159693 rowSearcher.getTerm = function getTerm(filter) {
94169694 if (typeof(filter.term) === 'undefined') { return filter.term; }
9417
9695
94189696 var term = filter.term;
94199697
94209698 // Strip leading and trailing whitespace if the term is a string
94439721 return term;
94449722 }
94459723 };
9446
9724
94479725
94489726 /**
94499727 * @ngdoc function
94629740 }
94639741
94649742 var term = rowSearcher.getTerm(filter);
9465
9743
94669744 if (/\*/.test(term)) {
94679745 var regexpFlags = '';
94689746 if (!filter.flags || !filter.flags.caseSensitive) {
94779755 return defaultCondition;
94789756 }
94799757 };
9480
9481
9758
9759
94829760 /**
94839761 * @ngdoc function
94849762 * @name setupFilters
94869764 * @description For a given columns filters (either col.filters, or [col.filter] can be passed in),
94879765 * do all the parsing and pre-processing and store that data into a new filters object. The object
94889766 * has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
9489 *
9490 * We could use a forEach in here, since it's much less performance sensitive, but since we're using
9767 *
9768 * We could use a forEach in here, since it's much less performance sensitive, but since we're using
94919769 * for loops everywhere else in this module...
9492 *
9770 *
94939771 * @param {array} filters the filters from the column (col.filters or [col.filter])
94949772 * @returns {array} An array of parsed/preprocessed filters
94959773 */
9496 rowSearcher.setupFilters = function setupFilters( filters ){
9774 rowSearcher.setupFilters = function setupFilters( filters ) {
94979775 var newFilters = [];
9498
9776
94999777 var filtersLength = filters.length;
9500 for ( var i = 0; i < filtersLength; i++ ){
9778 for ( var i = 0; i < filtersLength; i++ ) {
95019779 var filter = filters[i];
9502
9503 if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ){
9780
9781 if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ) {
95049782 var newFilter = {};
9505
9783
95069784 var regexpFlags = '';
95079785 if (!filter.flags || !filter.flags.caseSensitive) {
95089786 regexpFlags += 'i';
95099787 }
9510
9511 if ( !gridUtil.isNullOrUndefined(filter.term) ){
9512 // it is possible to have noTerm. We don't need to copy that across, it was just a flag to avoid
9513 // getting the filter ignored if the filter was a function that didn't use a term
9514 newFilter.term = rowSearcher.stripTerm(filter);
9515 }
9516
9517 if ( filter.condition ){
9788
9789 if ( !gridUtil.isNullOrUndefined(filter.term) ) {
9790 // it is possible to have noTerm.
9791 if ( filter.rawTerm ) {
9792 newFilter.term = filter.term;
9793 } else {
9794 newFilter.term = rowSearcher.stripTerm(filter);
9795 }
9796 }
9797 newFilter.noTerm = filter.noTerm;
9798
9799 if ( filter.condition ) {
95189800 newFilter.condition = filter.condition;
95199801 } else {
95209802 newFilter.condition = rowSearcher.guessCondition(filter);
95259807 if (newFilter.condition === uiGridConstants.filter.STARTS_WITH) {
95269808 newFilter.startswithRE = new RegExp('^' + newFilter.term, regexpFlags);
95279809 }
9528
9810
95299811 if (newFilter.condition === uiGridConstants.filter.ENDS_WITH) {
95309812 newFilter.endswithRE = new RegExp(newFilter.term + '$', regexpFlags);
95319813 }
95379819 if (newFilter.condition === uiGridConstants.filter.EXACT) {
95389820 newFilter.exactRE = new RegExp('^' + newFilter.term + '$', regexpFlags);
95399821 }
9540
9822
95419823 newFilters.push(newFilter);
95429824 }
95439825 }
95449826 return newFilters;
95459827 };
9546
9828
95479829
95489830 /**
95499831 * @ngdoc function
95519833 * @methodOf ui.grid.service:rowSearcher
95529834 * @description Runs a single pre-parsed filter against a cell, returning true
95539835 * if the cell matches that one filter.
9554 *
9836 *
95559837 * @param {Grid} grid the grid we're working against
95569838 * @param {GridRow} row the row we're matching against
9557 * @param {GridCol} column the column that we're working against
9839 * @param {GridColumn} column the column that we're working against
95589840 * @param {object} filter the specific, preparsed, filter that we want to test
95599841 * @returns {boolean} true if we match (row stays visible)
95609842 */
95679849
95689850 // Get the column value for this row
95699851 var value;
9570 if ( column.filterCellFiltered ){
9852 if ( column.filterCellFiltered ) {
95719853 value = grid.getCellDisplayValue(row, column);
95729854 } else {
95739855 value = grid.getCellValue(row, column);
96059887 return !regex.exec(value);
96069888 }
96079889
9608 if (typeof(value) === 'number' && typeof(term) === 'string' ){
9890 if (typeof(value) === 'number' && typeof(term) === 'string' ) {
96099891 // if the term has a decimal in it, it comes through as '9\.4', we need to take out the \
96109892 // the same for negative numbers
96119893 // TODO: I suspect the right answer is to look at escapeRegExp at the top of this code file, maybe it's not needed?
96479929 * @propertyOf ui.grid.class:GridOptions
96489930 * @description False by default. When enabled, this setting suppresses the internal filtering.
96499931 * All UI logic will still operate, allowing filter conditions to be set and modified.
9650 *
9932 *
96519933 * The external filter logic can listen for the `filterChange` event, which fires whenever
96529934 * a filter has been adjusted.
96539935 */
96559937 * @ngdoc function
96569938 * @name searchColumn
96579939 * @methodOf ui.grid.service:rowSearcher
9658 * @description Process provided filters on provided column against a given row. If the row meets
9940 * @description Process provided filters on provided column against a given row. If the row meets
96599941 * the conditions on all the filters, return true.
96609942 * @param {Grid} grid Grid to search in
96619943 * @param {GridRow} row Row to search on
9662 * @param {GridCol} column Column with the filters to use
9944 * @param {GridColumn} column Column with the filters to use
96639945 * @param {array} filters array of pre-parsed/preprocessed filters to apply
96649946 * @returns {boolean} Whether the column matches or not.
96659947 */
96729954 for (var i = 0; i < filtersLength; i++) {
96739955 var filter = filters[i];
96749956
9675 var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9676 if (!ret) {
9677 return false;
9957 if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ) {
9958 var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9959 if (!ret) {
9960 return false;
9961 }
96789962 }
96799963 }
96809964
96869970 * @ngdoc function
96879971 * @name search
96889972 * @methodOf ui.grid.service:rowSearcher
9689 * @description Run a search across the given rows and columns, marking any rows that don't
9973 * @description Run a search across the given rows and columns, marking any rows that don't
96909974 * match the stored col.filters or col.filter as invisible.
96919975 * @param {Grid} grid Grid instance to search inside
96929976 * @param {Array[GridRow]} rows GridRows to filter
97059989 }
97069990
97079991 // don't filter if filtering currently disabled
9708 if (!grid.options.enableFiltering){
9992 if (!grid.options.enableFiltering) {
97099993 return rows;
97109994 }
97119995
971810002 var hasTerm = false;
971910003
972010004 filters.forEach( function (filter) {
9721 if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
10005 if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ) {
972210006 hasTerm = true;
972310007 }
972410008 });
973610020
973710021 if (filterData.length > 0) {
973810022 // define functions outside the loop, performance optimisation
9739 var foreachRow = function(grid, row, col, filters){
10023 var foreachRow = function(grid, row, col, filters) {
974010024 if ( row.visible && !rowSearcher.searchColumn(grid, row, col, filters) ) {
974110025 row.visible = false;
974210026 }
974310027 };
974410028
9745 var foreachFilterCol = function(grid, filterData){
10029 var foreachFilterCol = function(grid, filterData) {
974610030 var rowsLength = rows.length;
9747 for ( var i = 0; i < rowsLength; i++){
9748 foreachRow(grid, rows[i], filterData.col, filterData.filters);
10031 for ( var i = 0; i < rowsLength; i++) {
10032 foreachRow(grid, rows[i], filterData.col, filterData.filters);
974910033 }
975010034 };
975110035
975210036 // nested loop itself - foreachFilterCol, which in turn calls foreachRow
975310037 var filterDataLength = filterData.length;
9754 for ( var j = 0; j < filterDataLength; j++){
9755 foreachFilterCol( grid, filterData[j] );
10038 for ( var j = 0; j < filterDataLength; j++) {
10039 foreachFilterCol( grid, filterData[j] );
975610040 }
975710041
975810042 if (grid.api.core.raise.rowsVisibleChanged) {
976110045
976210046 // drop any invisible rows
976310047 // keeping these, as needed with filtering for trees - we have to come back and make parent nodes visible if child nodes are selected in the filter
9764 // rows = rows.filter(function(row){ return row.visible; });
10048 // rows = rows.filter(function(row) { return row.visible; });
976510049
976610050 }
976710051
977910063
978010064 /**
978110065 * @ngdoc object
9782 * @name ui.grid.class:RowSorter
9783 * @description RowSorter provides the default sorting mechanisms,
9784 * including guessing column types and applying appropriate sort
10066 * @name ui.grid.class:rowSorter
10067 * @description rowSorter provides the default sorting mechanisms,
10068 * including guessing column types and applying appropriate sort
978510069 * algorithms
9786 *
9787 */
10070 *
10071 */
978810072
978910073 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
9790 var currencyRegexStr =
10074 var currencyRegexStr =
979110075 '(' +
979210076 uiGridConstants.CURRENCY_SYMBOLS
979310077 .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
980710091
980810092 /**
980910093 * @ngdoc method
9810 * @methodOf ui.grid.class:RowSorter
10094 * @methodOf ui.grid.class:rowSorter
981110095 * @name guessSortFn
981210096 * @description Assigns a sort function to use based on the itemType in the column
981310097 * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'. And
982910113 case "object":
983010114 return rowSorter.basicSort;
983110115 default:
9832 throw new Error('No sorting function found for type:' + itemType);
10116 throw new Error('No sorting function found for type: ' + itemType);
983310117 }
983410118 };
983510119
983610120
983710121 /**
983810122 * @ngdoc method
9839 * @methodOf ui.grid.class:RowSorter
10123 * @methodOf ui.grid.class:rowSorter
984010124 * @name handleNulls
984110125 * @description Sorts nulls and undefined to the bottom (top when
984210126 * descending). Called by each of the internal sorters before
986710151
986810152 /**
986910153 * @ngdoc method
9870 * @methodOf ui.grid.class:RowSorter
10154 * @methodOf ui.grid.class:rowSorter
987110155 * @name basicSort
987210156 * @description Sorts any values that provide the < method, including strings
9873 * or numbers. Handles nulls and undefined through calling handleNulls
10157 * or numbers. Handles nulls and undefined through calling handleNulls
987410158 * @param {object} a sort value a
987510159 * @param {object} b sort value b
987610160 * @returns {number} normal sort function, returns -ve, 0, +ve
987710161 */
987810162 rowSorter.basicSort = function basicSort(a, b) {
987910163 var nulls = rowSorter.handleNulls(a, b);
9880 if ( nulls !== null ){
10164 if ( nulls !== null ) {
988110165 return nulls;
988210166 } else {
988310167 if (a === b) {
989310177
989410178 /**
989510179 * @ngdoc method
9896 * @methodOf ui.grid.class:RowSorter
10180 * @methodOf ui.grid.class:rowSorter
989710181 * @name sortNumber
9898 * @description Sorts numerical values. Handles nulls and undefined through calling handleNulls
10182 * @description Sorts numerical values. Handles nulls and undefined through calling handleNulls
989910183 * @param {object} a sort value a
990010184 * @param {object} b sort value b
990110185 * @returns {number} normal sort function, returns -ve, 0, +ve
990210186 */
990310187 rowSorter.sortNumber = function sortNumber(a, b) {
990410188 var nulls = rowSorter.handleNulls(a, b);
9905 if ( nulls !== null ){
10189 if ( nulls !== null ) {
990610190 return nulls;
990710191 } else {
990810192 return a - b;
991210196
991310197 /**
991410198 * @ngdoc method
9915 * @methodOf ui.grid.class:RowSorter
10199 * @methodOf ui.grid.class:rowSorter
991610200 * @name sortNumberStr
9917 * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
9918 * Handles nulls and undefined through calling handleNulls
10201 * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
10202 * Handles nulls and undefined through calling handleNulls
991910203 * @param {object} a sort value a
992010204 * @param {object} b sort value b
992110205 * @returns {number} normal sort function, returns -ve, 0, +ve
992210206 */
992310207 rowSorter.sortNumberStr = function sortNumberStr(a, b) {
992410208 var nulls = rowSorter.handleNulls(a, b);
9925 if ( nulls !== null ){
10209 if ( nulls !== null ) {
992610210 return nulls;
992710211 } else {
992810212 var numA, // The parsed number form of 'a'
992910213 numB, // The parsed number form of 'b'
993010214 badA = false,
993110215 badB = false;
9932
10216
993310217 // Try to parse 'a' to a float
993410218 numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
9935
10219
993610220 // If 'a' couldn't be parsed to float, flag it as bad
993710221 if (isNaN(numA)) {
993810222 badA = true;
993910223 }
9940
10224
994110225 // Try to parse 'b' to a float
994210226 numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
9943
10227
994410228 // If 'b' couldn't be parsed to float, flag it as bad
994510229 if (isNaN(numB)) {
994610230 badB = true;
994710231 }
9948
10232
994910233 // We want bad ones to get pushed to the bottom... which effectively is "greater than"
995010234 if (badA && badB) {
995110235 return 0;
995210236 }
9953
10237
995410238 if (badA) {
995510239 return 1;
995610240 }
9957
10241
995810242 if (badB) {
995910243 return -1;
996010244 }
9961
10245
996210246 return numA - numB;
996310247 }
996410248 };
996610250
996710251 /**
996810252 * @ngdoc method
9969 * @methodOf ui.grid.class:RowSorter
10253 * @methodOf ui.grid.class:rowSorter
997010254 * @name sortAlpha
9971 * @description Sorts string values. Handles nulls and undefined through calling handleNulls
10255 * @description Sorts string values. Handles nulls and undefined through calling handleNulls
997210256 * @param {object} a sort value a
997310257 * @param {object} b sort value b
997410258 * @returns {number} normal sort function, returns -ve, 0, +ve
997510259 */
997610260 rowSorter.sortAlpha = function sortAlpha(a, b) {
997710261 var nulls = rowSorter.handleNulls(a, b);
9978 if ( nulls !== null ){
10262 if ( nulls !== null ) {
997910263 return nulls;
998010264 } else {
998110265 var strA = a.toString().toLowerCase(),
998210266 strB = b.toString().toLowerCase();
9983
9984 return strA === strB ? 0 : (strA < strB ? -1 : 1);
10267
10268 return strA === strB ? 0 : strA.localeCompare(strB);
998510269 }
998610270 };
998710271
998810272
998910273 /**
999010274 * @ngdoc method
9991 * @methodOf ui.grid.class:RowSorter
10275 * @methodOf ui.grid.class:rowSorter
999210276 * @name sortDate
999310277 * @description Sorts date values. Handles nulls and undefined through calling handleNulls.
999410278 * Handles date strings by converting to Date object if not already an instance of Date
999810282 */
999910283 rowSorter.sortDate = function sortDate(a, b) {
1000010284 var nulls = rowSorter.handleNulls(a, b);
10001 if ( nulls !== null ){
10285 if ( nulls !== null ) {
1000210286 return nulls;
1000310287 } else {
1000410288 if (!(a instanceof Date)) {
1000510289 a = new Date(a);
1000610290 }
10007 if (!(b instanceof Date)){
10291 if (!(b instanceof Date)) {
1000810292 b = new Date(b);
1000910293 }
1001010294 var timeA = a.getTime(),
1001110295 timeB = b.getTime();
10012
10296
1001310297 return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
1001410298 }
1001510299 };
1001710301
1001810302 /**
1001910303 * @ngdoc method
10020 * @methodOf ui.grid.class:RowSorter
10304 * @methodOf ui.grid.class:rowSorter
1002110305 * @name sortBool
10022 * @description Sorts boolean values, true is considered larger than false.
10023 * Handles nulls and undefined through calling handleNulls
10306 * @description Sorts boolean values, true is considered larger than false.
10307 * Handles nulls and undefined through calling handleNulls
1002410308 * @param {object} a sort value a
1002510309 * @param {object} b sort value b
1002610310 * @returns {number} normal sort function, returns -ve, 0, +ve
1002710311 */
1002810312 rowSorter.sortBool = function sortBool(a, b) {
1002910313 var nulls = rowSorter.handleNulls(a, b);
10030 if ( nulls !== null ){
10314 if ( nulls !== null ) {
1003110315 return nulls;
1003210316 } else {
1003310317 if (a && b) {
1003410318 return 0;
1003510319 }
10036
10320
1003710321 if (!a && !b) {
1003810322 return 0;
1003910323 }
1004610330
1004710331 /**
1004810332 * @ngdoc method
10049 * @methodOf ui.grid.class:RowSorter
10333 * @methodOf ui.grid.class:rowSorter
1005010334 * @name getSortFn
10051 * @description Get the sort function for the column. Looks first in
10335 * @description Get the sort function for the column. Looks first in
1005210336 * rowSorter.colSortFnCache using the column name, failing that it
1005310337 * looks at col.sortingAlgorithm (and puts it in the cache), failing that
1005410338 * it guesses the sort algorithm based on the data type.
10055 *
10339 *
1005610340 * The cache currently seems a bit pointless, as none of the work we do is
1005710341 * processor intensive enough to need caching. Presumably in future we might
1005810342 * inspect the row data itself to guess the sort function, and in that case
1005910343 * it would make sense to have a cache, the infrastructure is in place to allow
1006010344 * that.
10061 *
10345 *
1006210346 * @param {Grid} grid the grid to consider
10063 * @param {GridCol} col the column to find a function for
10347 * @param {GridColumn} col the column to find a function for
1006410348 * @param {array} rows an array of grid rows. Currently unused, but presumably in future
1006510349 * we might inspect the rows themselves to decide what sort of data might be there
1006610350 * @returns {function} the sort function chosen for the column
1007810362 rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
1007910363 }
1008010364 // Always default to sortAlpha when sorting after a cellFilter
10081 else if ( col.sortCellFiltered && col.cellFilter ){
10365 else if ( col.sortCellFiltered && col.cellFilter ) {
1008210366 sortFn = rowSorter.sortAlpha;
1008310367 rowSorter.colSortFnCache[col.colDef.name] = sortFn;
1008410368 }
1010610390
1010710391 /**
1010810392 * @ngdoc method
10109 * @methodOf ui.grid.class:RowSorter
10393 * @methodOf ui.grid.class:rowSorter
1011010394 * @name prioritySort
1011110395 * @description Used where multiple columns are present in the sort criteria,
1011210396 * we determine which column should take precedence in the sort by sorting
1011310397 * the columns based on their sort.priority
10114 *
10398 *
1011510399 * @param {gridColumn} a column a
1011610400 * @param {gridColumn} b column b
1011710401 * @returns {number} normal sort function, returns -ve, 0, +ve
1013310417 }
1013410418 }
1013510419 // Only A has a priority
10136 else if (a.sort.priority || a.sort.priority === 0) {
10420 else if (a.sort.priority !== undefined) {
1013710421 return -1;
1013810422 }
1013910423 // Only B has a priority
10140 else if (b.sort.priority || b.sort.priority === 0) {
10424 else if (b.sort.priority !== undefined) {
1014110425 return 1;
1014210426 }
1014310427 // Neither has a priority
1015410438 * @description Prevents the internal sorting from executing. Events will
1015510439 * still be fired when the sort changes, and the sort information on
1015610440 * the columns will be updated, allowing an external sorter (for example,
10157 * server sorting) to be implemented. Defaults to false.
10158 *
10441 * server sorting) to be implemented. Defaults to false.
10442 *
1015910443 */
1016010444 /**
1016110445 * @ngdoc method
10162 * @methodOf ui.grid.class:RowSorter
10446 * @methodOf ui.grid.class:rowSorter
1016310447 * @name sort
10164 * @description sorts the grid
10448 * @description sorts the grid
1016510449 * @param {Object} grid the grid itself
1016610450 * @param {array} rows the rows to be sorted
1016710451 * @param {array} columns the columns in which to look
1017310457 if (!rows) {
1017410458 return;
1017510459 }
10176
10177 if (grid.options.useExternalSorting){
10460
10461 if (grid.options.useExternalSorting) {
1017810462 return rows;
1017910463 }
1018010464
1018110465 // Build the list of columns to sort by
1018210466 var sortCols = [];
10467 var defaultSortCols = [];
1018310468 columns.forEach(function (col) {
1018410469 if (col.sort && !col.sort.ignoreSort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
10185 sortCols.push(col);
10470 sortCols.push({
10471 col: col,
10472 sort: col.sort
10473 });
10474 } else if ( col.defaultSort && col.defaultSort.direction && (col.defaultSort.direction === uiGridConstants.ASC || col.defaultSort.direction === uiGridConstants.DESC) ) {
10475 defaultSortCols.push({
10476 col: col,
10477 sort: col.defaultSort
10478 });
1018610479 }
1018710480 });
1018810481
1018910482 // Sort the "sort columns" by their sort priority
1019010483 sortCols = sortCols.sort(rowSorter.prioritySort);
10484 defaultSortCols = defaultSortCols.sort(rowSorter.prioritySort);
10485 sortCols = sortCols.concat(defaultSortCols);
1019110486
1019210487 // Now rows to sort by, maintain original order
1019310488 if (sortCols.length === 0) {
1019810493 var col, direction;
1019910494
1020010495 // put a custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10201 var setIndex = function( row, idx ){
10496 var setIndex = function( row, idx ) {
1020210497 row.entity.$$uiGridIndex = idx;
1020310498 };
1020410499 rows.forEach(setIndex);
1021510510
1021610511 while (tem === 0 && idx < sortCols.length) {
1021710512 // grab the metadata for the rest of the logic
10218 col = sortCols[idx];
10513 col = sortCols[idx].col;
1021910514 direction = sortCols[idx].sort.direction;
1022010515
1022110516 sortFn = rowSorter.getSortFn(grid, col, r);
1022210517
1022310518 var propA, propB;
1022410519
10225 if ( col.sortCellFiltered ){
10520 if ( col.sortCellFiltered ) {
1022610521 propA = grid.getCellDisplayValue(rowA, col);
1022710522 propB = grid.getCellDisplayValue(rowB, col);
1022810523 } else {
1023010525 propB = grid.getCellValue(rowB, col);
1023110526 }
1023210527
10233 tem = sortFn(propA, propB, rowA, rowB, direction);
10528 tem = sortFn(propA, propB, rowA, rowB, direction, col);
1023410529
1023510530 idx++;
1023610531 }
1023710532
10238 // Chrome doesn't implement a stable sort function. If our sort returns 0
10533 // Chrome doesn't implement a stable sort function. If our sort returns 0
1023910534 // (i.e. the items are equal), and we're at the last sort column in the list,
1024010535 // then return the previous order using our custom
1024110536 // index variable
1025210547 };
1025310548
1025410549 var newRows = rows.sort(rowSortFn);
10255
10550
1025610551 // remove the custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10257 var clearIndex = function( row, idx ){
10552 var clearIndex = function( row, idx ) {
1025810553 delete row.entity.$$uiGridIndex;
1025910554 };
1026010555 rows.forEach(clearIndex);
10261
10556
1026210557 return newRows;
1026310558 };
1026410559
1029010585 };
1029110586 }
1029210587
10293 function getStyles (elem) {
10588 function getStyles (elem) {
1029410589 var e = elem;
1029510590 if (typeof(e.length) !== 'undefined' && e.length) {
1029610591 e = elem[0];
1045110746 * @name createBoundedWrapper
1045210747 * @methodOf ui.grid.service:GridUtil
1045310748 *
10454 * @param {object} Object to bind 'this' to
10455 * @param {method} Method to bind
10749 * @param {object} object to bind 'this' to
10750 * @param {method} method to bind
1045610751 * @returns {Function} The wrapper that performs the binding
1045710752 *
1045810753 * @description
1051210807 return columnName.replace(/_+/g, ' ')
1051310808 // Replace a completely all-capsed word with a first-letter-capitalized version
1051410809 .replace(/^[A-Z]+$/, function (match) {
10515 return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
10810 return match.toLowerCase();
1051610811 })
1051710812 // Capitalize the first letter of words
1051810813 .replace(/([\w\u00C0-\u017F]+)/g, function (match) {
10519 return angular.uppercase(match.charAt(0)) + match.slice(1);
10814 return match.charAt(0).toUpperCase() + match.slice(1);
1052010815 })
1052110816 // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
1052210817 // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
1056310858 var item = data[0];
1056410859
1056510860 angular.forEach(item,function (prop, propName) {
10566 if ( excludeProperties.indexOf(propName) === -1){
10861 if ( excludeProperties.indexOf(propName) === -1) {
1056710862 columnDefs.push({
1056810863 name: propName
1056910864 });
1060210897 * @methodOf ui.grid.service:GridUtil
1060310898 * @description Get's template from cache / element / url
1060410899 *
10605 * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
10900 * @param {string|element|promise} template Either a string representing the template id, a string representing the template url,
1060610901 * an jQuery/Angualr element, or a promise that returns the template contents to use.
1060710902 * @returns {object} a promise resolving to template contents
1060810903 *
1062010915 }
1062110916
1062210917 // See if the template is itself a promise
10623 if (template.hasOwnProperty('then')) {
10624 return template.then(s.postProcessTemplate);
10918 if (angular.isFunction(template.then)) {
10919 return template.then(s.postProcessTemplate).catch(angular.noop);
1062510920 }
1062610921
1062710922 // If the template is an element, return the element
1062810923 try {
1062910924 if (angular.element(template).length > 0) {
10630 return $q.when(template).then(s.postProcessTemplate);
10925 return $q.when(template).then(s.postProcessTemplate).catch(angular.noop);
1063110926 }
1063210927 }
10633 catch (err){
10634 //do nothing; not valid html
10928 catch (err) {
10929 // do nothing; not valid html
1063510930 }
1063610931
10637 s.logDebug('fetching url', template);
10932 // s.logDebug('fetching url', template);
1063810933
1063910934 // Default to trying to fetch the template as a url with $http
1064010935 return $http({ method: 'GET', url: template})
1064110936 .then(
1064210937 function (result) {
1064310938 var templateHtml = result.data.trim();
10644 //put in templateCache for next call
10939 // put in templateCache for next call
1064510940 $templateCache.put(template, templateHtml);
1064610941 return templateHtml;
1064710942 },
1064910944 throw new Error("Could not get template " + template + ": " + err);
1065010945 }
1065110946 )
10652 .then(s.postProcessTemplate);
10947 .then(s.postProcessTemplate).catch(angular.noop);
1065310948 },
1065410949
1065510950 //
1070310998 * @name elementWidth
1070410999 * @methodOf ui.grid.service:GridUtil
1070511000 *
10706 * @param {element} element DOM element
11001 * @param {element} elem DOM element
1070711002 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
1070811003 *
1070911004 * @returns {number} Element width in pixels, accounting for any borders, etc.
1071711012 * @name elementHeight
1071811013 * @methodOf ui.grid.service:GridUtil
1071911014 *
10720 * @param {element} element DOM element
11015 * @param {element} elem DOM element
1072111016 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
1072211017 *
1072311018 * @returns {number} Element height in pixels, accounting for any borders, etc.
1072911024 // Thanks to http://stackoverflow.com/a/13382873/888165
1073011025 getScrollbarWidth: function() {
1073111026 var outer = document.createElement("div");
11027
1073211028 outer.style.visibility = "hidden";
1073311029 outer.style.width = "100px";
1073411030 outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
1087611172
1087711173 // Stolen from Modernizr
1087811174 // TODO: make this, and everythign that flows from it, robust
10879 //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
11175 // http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
1088011176 isTouchEnabled: function() {
1088111177 var bool;
1088211178
1088811184 },
1088911185
1089011186 isNullOrUndefined: function(obj) {
10891 if (obj === undefined || obj === null) {
10892 return true;
10893 }
10894 return false;
11187 return (obj === undefined || obj === null);
1089511188 },
1089611189
1089711190 endsWith: function(str, suffix) {
1091011203 });
1091111204 return found;
1091211205 },
10913
10914 //// Shim requestAnimationFrame
10915 //requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
10916 // $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
10917 // function(fn) {
10918 // return $timeout(fn, 10, false);
10919 // },
1092011206
1092111207 numericAndNullSort: function (a, b) {
1092211208 if (a === null) { return 1; }
1095511241 catch (e) {}
1095611242 },
1095711243
10958 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11244 // Blatantly stolen from Angular as it isn't exposed (yet)
1095911245 nextUid: function nextUid() {
1096011246 var index = uid.length;
1096111247 var digit;
1097911265 return uidPrefix + uid.join('');
1098011266 },
1098111267
10982 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11268 // Blatantly stolen from Angular as it isn't exposed (yet)
1098311269 hashKey: function hashKey(obj) {
1098411270 var objType = typeof obj,
1098511271 key;
1100011286 key = obj;
1100111287 }
1100211288
11003 return objType + ':' + key;
11289 return objType + ': ' + key;
1100411290 },
1100511291
1100611292 resetUids: function () {
1101711303 * @param {string} logMessage message to be logged to the console
1101811304 *
1101911305 */
11020 logError: function( logMessage ){
11021 if ( uiGridConstants.LOG_ERROR_MESSAGES ){
11306 logError: function( logMessage ) {
11307 if ( uiGridConstants.LOG_ERROR_MESSAGES ) {
1102211308 $log.error( logMessage );
1102311309 }
1102411310 },
1103311319 * @param {string} logMessage message to be logged to the console
1103411320 *
1103511321 */
11036 logWarn: function( logMessage ){
11037 if ( uiGridConstants.LOG_WARN_MESSAGES ){
11322 logWarn: function( logMessage ) {
11323 if ( uiGridConstants.LOG_WARN_MESSAGES ) {
1103811324 $log.warn( logMessage );
1103911325 }
1104011326 },
1104911335 *
1105011336 */
1105111337 logDebug: function() {
11052 if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
11338 if ( uiGridConstants.LOG_DEBUG_MESSAGES ) {
1105311339 $log.debug.apply($log, arguments);
1105411340 }
1105511341 }
1106011346 * @ngdoc object
1106111347 * @name focus
1106211348 * @propertyOf ui.grid.service:GridUtil
11063 * @description Provies a set of methods to set the document focus inside the grid.
11349 * @description Provides a set of methods to set the document focus inside the grid.
1106411350 * See {@link ui.grid.service:GridUtil.focus} for more information.
1106511351 */
1106611352
1106711353 /**
1106811354 * @ngdoc object
1106911355 * @name ui.grid.service:GridUtil.focus
11070 * @description Provies a set of methods to set the document focus inside the grid.
11356 * @description Provides a set of methods to set the document focus inside the grid.
1107111357 * Timeouts are utilized to ensure that the focus is invoked after any other event has been triggered.
1107211358 * e.g. click events that need to run before the focus or
1107311359 * inputs elements that are in a disabled state but are enabled when those events
1107511361 */
1107611362 s.focus = {
1107711363 queue: [],
11078 //http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
11364 // http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
1107911365 /**
1108011366 * @ngdoc method
1108111367 * @methodOf ui.grid.service:GridUtil.focus
1110011386 } else {
1110111387 s.logWarn('[focus.byId] Element id ' + elementID + ' was not found.');
1110211388 }
11103 });
11389 }, 0, false);
1110411390 this.queue.push(promise);
1110511391 return promise;
1110611392 },
1111411400 * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
1111511401 * then the promise will fail with the `'canceled'` reason.
1111611402 */
11117 byElement: function(element){
11118 if (!angular.isElement(element)){
11403 byElement: function(element) {
11404 if (!angular.isElement(element)) {
1111911405 s.logWarn("Trying to focus on an element that isn\'t an element.");
1112011406 return $q.reject('not-element');
1112111407 }
1112211408 element = angular.element(element);
1112311409 this._purgeQueue();
11124 var promise = $timeout(function(){
11125 if (element){
11410 var promise = $timeout(function() {
11411 if (element) {
1112611412 element[0].focus();
1112711413 }
11128 });
11414 }, 0, false);
1112911415 this.queue.push(promise);
1113011416 return promise;
1113111417 },
1114111427 * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
1114211428 * then the promise will fail with the `'canceled'` reason.
1114311429 */
11144 bySelector: function(parentElement, querySelector, aSync){
11430 bySelector: function(parentElement, querySelector, aSync) {
1114511431 var self = this;
11146 if (!angular.isElement(parentElement)){
11432 if (!angular.isElement(parentElement)) {
1114711433 throw new Error("The parent element is not an element.");
1114811434 }
1114911435 // Ensure that this is an angular element.
1115011436 // It is fine if this is already an angular element.
1115111437 parentElement = angular.element(parentElement);
11152 var focusBySelector = function(){
11438 var focusBySelector = function() {
1115311439 var element = parentElement[0].querySelector(querySelector);
1115411440 return self.byElement(element);
1115511441 };
1115611442 this._purgeQueue();
11157 if (aSync){ //Do this asynchronysly
11158 var promise = $timeout(focusBySelector);
11159 this.queue.push($timeout(focusBySelector));
11443 if (aSync) { // Do this asynchronysly
11444 var promise = $timeout(focusBySelector, 0, false);
11445 this.queue.push(promise);
1116011446 return promise;
1116111447 } else {
1116211448 return focusBySelector();
1116311449 }
1116411450 },
11165 _purgeQueue: function(){
11166 this.queue.forEach(function(element){
11451 _purgeQueue: function() {
11452 this.queue.forEach(function(element) {
1116711453 $timeout.cancel(element);
1116811454 });
1116911455 this.queue = [];
1117211458
1117311459
1117411460 ['width', 'height'].forEach(function (name) {
11175 var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
11461 var capsName = name.charAt(0).toUpperCase() + name.substr(1);
11462
1117611463 s['element' + capsName] = function (elem, extra) {
1117711464 var e = elem;
1117811465 if (e && typeof(e.length) !== 'undefined' && e.length) {
1117911466 e = elem[0];
1118011467 }
1118111468
11182 if (e) {
11469 if (e && e !== null) {
1118311470 var styles = getStyles(e);
1118411471 return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
11185 s.swap(e, cssShow, function() {
11186 return getWidthOrHeight(e, name, extra );
11187 }) :
11188 getWidthOrHeight( e, name, extra );
11189 }
11190 else {
11472 s.swap(e, cssShow, function() {
11473 return getWidthOrHeight(e, name, extra );
11474 }) :
11475 getWidthOrHeight( e, name, extra );
11476 } else {
1119111477 return null;
1119211478 }
1119311479 };
1142211708 * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
1142311709 * @example
1142411710 * <pre>
11425 * var debouncedFunc = gridUtil.debounce(function(){alert('debounced');}, 500);
11711 * var debouncedFunc = gridUtil.debounce(function() {alert('debounced');}, 500);
1142611712 * debouncedFunc();
1142711713 * debouncedFunc();
1142811714 * debouncedFunc();
1143111717 s.debounce = function (func, wait, immediate) {
1143211718 var timeout, args, context, result;
1143311719 function debounce() {
11434 /* jshint validthis:true */
11720 /* jshint validthis: true */
1143511721 context = this;
1143611722 args = arguments;
1143711723 var later = function () {
1144411730 if (timeout) {
1144511731 $timeout.cancel(timeout);
1144611732 }
11447 timeout = $timeout(later, wait);
11733 timeout = $timeout(later, wait, false);
1144811734 if (callNow) {
1144911735 result = func.apply(context, args);
1145011736 }
1146411750 *
1146511751 * @param {function} func function to throttle
1146611752 * @param {number} wait milliseconds to delay after first trigger
11467 * @param {Object} params to use in throttle.
11753 * @param {Object} options to use in throttle.
1146811754 *
1146911755 * @returns {function} A function that can be executed as throttled function
1147011756 *
1148111767 *
1148211768 * @example
1148311769 * <pre>
11484 * var throttledFunc = gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
11770 * var throttledFunc = gridUtil.throttle(function() {console.log('throttled');}, 500, {trailing: true});
1148511771 * throttledFunc(); //=> logs throttled
1148611772 * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
1148711773 * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
1148811774 * </pre>
1148911775 */
11490 s.throttle = function(func, wait, options){
11776 s.throttle = function(func, wait, options) {
1149111777 options = options || {};
1149211778 var lastCall = 0, queued = null, context, args;
1149311779
11494 function runFunc(endDate){
11780 function runFunc(endDate) {
1149511781 lastCall = +new Date();
1149611782 func.apply(context, args);
11497 $interval(function(){ queued = null; }, 0, 1);
11783 $interval(function() {queued = null; }, 0, 1, false);
1149811784 }
1149911785
11500 return function(){
11501 /* jshint validthis:true */
11786 return function() {
11787 /* jshint validthis: true */
1150211788 context = this;
1150311789 args = arguments;
11504 if (queued === null){
11790 if (queued === null) {
1150511791 var sinceLast = +new Date() - lastCall;
11506 if (sinceLast > wait){
11792 if (sinceLast > wait) {
1150711793 runFunc();
1150811794 }
11509 else if (options.trailing){
11510 queued = $interval(runFunc, wait - sinceLast, 1);
11795 else if (options.trailing) {
11796 queued = $interval(runFunc, wait - sinceLast, 1, false);
1151111797 }
1151211798 }
1151311799 };
1154711833 for ( var i = mouseWheeltoBind.length; i; ) {
1154811834 $elm.on(mouseWheeltoBind[--i], cbs[fn]);
1154911835 }
11836 $elm.on('$destroy', function unbindEvents() {
11837 for ( var i = mouseWheeltoBind.length; i; ) {
11838 $elm.off(mouseWheeltoBind[--i], cbs[fn]);
11839 }
11840 });
1155011841 };
1155111842 s.off.mousewheel = function (elm, fn) {
1155211843 var $elm = angular.element(elm);
1164411935 deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
1164511936 deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
1164611937
11647 event.deltaMode = 0;
11648
11649 // Normalise offsetX and offsetY properties
11650 // if ($elm[0].getBoundingClientRect ) {
11651 // var boundingRect = $(elm)[0].getBoundingClientRect();
11652 // offsetX = event.clientX - boundingRect.left;
11653 // offsetY = event.clientY - boundingRect.top;
11654 // }
11655
11656 // event.deltaX = deltaX;
11657 // event.deltaY = deltaY;
11658 // event.deltaFactor = lowestDelta;
11659
1166011938 var newEvent = {
1166111939 originalEvent: event,
1166211940 deltaX: deltaX,
1170611984 };
1170711985 });
1170811986
11987 })();
11988
11989 (function() {
11990 angular.module('ui.grid').config(['$provide', function($provide) {
11991 $provide.decorator('i18nService', ['$delegate', function($delegate) {
11992 $delegate.add('ar', {
11993 "headerCell": {
11994 "aria": {
11995 "defaultFilterLabel": "التصفيه بالعمود",
11996 "removeFilter": "محو التصفيه",
11997 "columnMenuButtonLabel": "قاءمه الاعمده"
11998 },
11999 "priority": "أولويه : ",
12000 "filterLabel": "تصفيه بالاعمده :"
12001 },
12002 "aggregate": {
12003 "label": "العناصر"
12004 },
12005 "groupPanel": {
12006 "description": "اسحب رأس العمود هنا وأسقطه لإنشاء مجموعه"
12007 },
12008 "search": {
12009 "placeholder": "بحث ...",
12010 "showingItems": "العناصر الظاهره :",
12011 "selectedItems": "العناصر المحدده :",
12012 "totalItems": "عدد العناصر :",
12013 "size": "حجم الصفحه :",
12014 "first": "اول صفحه",
12015 "next": "الصفحه التاليه",
12016 "previous": "الصفحه الصابقه",
12017 "last": "الصفحه الاخيره"
12018 },
12019 "menu": {
12020 "text": "اختيار العمود :"
12021 },
12022 "sort": {
12023 "ascending": "ترتيب تصاعدى",
12024 "descending": "ترتيب تنازلى",
12025 "none": "عدم التحديد",
12026 "remove": "حذف الترتيب"
12027 },
12028 "column": {
12029 "hide": "إخفاء عمود"
12030 },
12031 "aggregation": {
12032 "count": "عدد الصفوف: ",
12033 "sum": "جمع: ",
12034 "avg": "المتوسط الحسابى: ",
12035 "min": "الادنى: ",
12036 "max": "الاقصى: "
12037 },
12038 "pinning": {
12039 "pinLeft": "تثبيت لليسار",
12040 "pinRight": "تثبيت لليمين",
12041 "unpin": "فك التثبيت"
12042 },
12043 "columnMenu": {
12044 "close": "غلق"
12045 },
12046 "gridMenu": {
12047 "aria": {
12048 "buttonLabel": "قائمه الجدول"
12049 },
12050 "columns": "الاعمده:",
12051 "importerTitle": "إدخال ملف",
12052 "exporterAllAsCsv": "إخراج كل البيانات ك(csv)",
12053 "exporterVisibleAsCsv": "إخراج كل البيانات الواضحه ك (csv)",
12054 "exporterSelectedAsCsv": "إخراج كل البيانات المحدده ك (csv)",
12055 "exporterAllAsPdf": "إخراج كل البيانات ك(pdf)",
12056 "exporterVisibleAsPdf": "إخراج كل البيانات الواضحه ك (pdf)",
12057 "exporterSelectedAsPdf": "إخراج كل البيانات المحدده ك (pdf)",
12058 "clearAllFilters": "محو كل الترشيح"
12059 },
12060 "importer": {
12061 "noHeaders": "اسماء هؤلاء الاعمده غير واضحه، هل يوجد رأس للملف؟",
12062 "noObjects": "Objects were not able to be derived, was there data in the file other than headers?",
12063 "invalidCsv": "الملف غير قادر على الاتمام ، هل ال (CSV) صحيح؟",
12064 "invalidJson": "الملف غير قادر على الاتمام ، هل ال (JSON) صحيح؟",
12065 "jsonNotArray": "Imported json file must contain an array, aborting."
12066 },
12067 "pagination": {
12068 "aria": {
12069 "pageToFirst": "الصفحه الاولى",
12070 "pageBack": "الصفه السابقه",
12071 "pageSelected": "الصفحه المحدده",
12072 "pageForward": "الصفحه التاليه",
12073 "pageToLast": "الصفحه الاخيره"
12074 },
12075 "sizes": "عدد العناصر فى الصفحه",
12076 "totalItems": "عناصر",
12077 "through": "إلى",
12078 "of": "من"
12079 },
12080 "grouping": {
12081 "group": "جمع",
12082 "ungroup": "فك الجمع",
12083 "aggregate_count": "جمله : العدد",
12084 "aggregate_sum": "جمله : الحاصل",
12085 "aggregate_max": "جمله : الاقصى",
12086 "aggregate_min": "جمله : الاقل",
12087 "aggregate_avg": "جمله :المتوسط ",
12088 "aggregate_remove": "جمله : حذف"
12089 },
12090 "validate": {
12091 "error": "خطأ :",
12092 "minLength": "القيمه لابد ان لا تقل عن THRESHOLD حرف.",
12093 "maxLength": "القيمه لابد ان لا تزيد عن THRESHOLD حرف.",
12094 "required": "مطلوب قيمه"
12095 }
12096 });
12097 return $delegate;
12098 }]);
12099 }]);
12100 })();
12101
12102 (function () {
12103 angular.module('ui.grid').config(['$provide', function($provide) {
12104 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12105 $delegate.add('bg', {
12106 headerCell: {
12107 aria: {
12108 defaultFilterLabel: 'Филттър за колоната',
12109 removeFilter: 'Премахни филтър',
12110 columnMenuButtonLabel: 'Меню на колоната'
12111 },
12112 priority: 'Приоритет:',
12113 filterLabel: "Филтър за колоната: "
12114 },
12115 aggregate: {
12116 label: 'обекти'
12117 },
12118 search: {
12119 placeholder: 'Търсене...',
12120 showingItems: 'Показани обекти:',
12121 selectedItems: 'избрани обекти:',
12122 totalItems: 'Общо:',
12123 size: 'Размер на страницата:',
12124 first: 'Първа страница',
12125 next: 'Следваща страница',
12126 previous: 'Предишна страница',
12127 last: 'Последна страница'
12128 },
12129 menu: {
12130 text: 'Избери колони:'
12131 },
12132 sort: {
12133 ascending: 'Сортиране по възходящ ред',
12134 descending: 'Сортиране по низходящ ред',
12135 none: 'Без сортиране',
12136 remove: 'Премахни сортирането'
12137 },
12138 column: {
12139 hide: 'Скрий колоната'
12140 },
12141 aggregation: {
12142 count: 'Общо редове: ',
12143 sum: 'общо: ',
12144 avg: 'средно: ',
12145 min: 'най-малко: ',
12146 max: 'най-много: '
12147 },
12148 pinning: {
12149 pinLeft: 'Прикрепи вляво',
12150 pinRight: 'Прикрепи вдясно',
12151 unpin: 'Премахване'
12152 },
12153 columnMenu: {
12154 close: 'Затвори'
12155 },
12156 gridMenu: {
12157 aria: {
12158 buttonLabel: 'Меню на таблицата'
12159 },
12160 columns: 'Колони:',
12161 importerTitle: 'Импортиране на файл',
12162 exporterAllAsCsv: 'Експортиране на данните като csv',
12163 exporterVisibleAsCsv: 'Експортиране на видимите данни като csv',
12164 exporterSelectedAsCsv: 'Експортиране на избраните данни като csv',
12165 exporterAllAsPdf: 'Експортиране на данните като pdf',
12166 exporterVisibleAsPdf: 'Експортиране на видимите данни като pdf',
12167 exporterSelectedAsPdf: 'Експортиране на избраните данни като pdf',
12168 clearAllFilters: 'Премахни всички филтри'
12169 },
12170 importer: {
12171 noHeaders: 'Имената на колоните не успяха да бъдат извлечени, файлът има ли хедър?',
12172 noObjects: 'Обектите не успяха да бъдат извлечени, файлът съдържа ли данни, различни от хедър?',
12173 invalidCsv: 'Файлът не може да бъде обработеб, уверете се, че е валиден CSV файл',
12174 invalidJson: 'Файлът не може да бъде обработеб, уверете се, че е валиден JSON файл',
12175 jsonNotArray: 'Импортираният JSON файл трябва да съдържа масив, прекратяване.'
12176 },
12177 pagination: {
12178 aria: {
12179 pageToFirst: 'Към първа страница',
12180 pageBack: 'Страница назад',
12181 pageSelected: 'Избрана страница',
12182 pageForward: 'Страница напред',
12183 pageToLast: 'Към последна страница'
12184 },
12185 sizes: 'обекта на страница',
12186 totalItems: 'обекта',
12187 through: 'до',
12188 of: 'от'
12189 },
12190 grouping: {
12191 group: 'Групиране',
12192 ungroup: 'Премахване на групирането',
12193 aggregate_count: 'Сбор: Брой',
12194 aggregate_sum: 'Сбор: Сума',
12195 aggregate_max: 'Сбор: Максимум',
12196 aggregate_min: 'Сбор: Минимум',
12197 aggregate_avg: 'Сбор: Средно',
12198 aggregate_remove: 'Сбор: Премахване'
12199 },
12200 validate: {
12201 error: 'Грешка:',
12202 minLength: 'Стойността трябва да съдържа поне THRESHOLD символа.',
12203 maxLength: 'Стойността не трябва да съдържа повече от THRESHOLD символа.',
12204 required: 'Необходима е стойност.'
12205 }
12206 });
12207 return $delegate;
12208 }]);
12209 }]);
1170912210 })();
1171012211
1171112212 (function () {
1176112262 exporterAllAsPdf: 'Exportovat všechna data do pdf',
1176212263 exporterVisibleAsPdf: 'Exportovat viditelná data do pdf',
1176312264 exporterSelectedAsPdf: 'Exportovat vybraná data do pdf',
12265 exporterAllAsExcel: 'Exportovat všechna data do excel',
12266 exporterVisibleAsExcel: 'Exportovat viditelná data do excel',
12267 exporterSelectedAsExcel: 'Exportovat vybraná data do excel',
1176412268 clearAllFilters: 'Odstranit všechny filtry'
1176512269 },
1176612270 importer: {
1179612300 }]);
1179712301 })();
1179812302
11799 (function(){
11800 angular.module('ui.grid').config(['$provide', function($provide) {
11801 $provide.decorator('i18nService', ['$delegate', function($delegate) {
11802 $delegate.add('da', {
11803 aggregate:{
11804 label: 'artikler'
11805 },
11806 groupPanel:{
11807 description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
11808 },
11809 search:{
11810 placeholder: 'Søg...',
11811 showingItems: 'Viste rækker:',
11812 selectedItems: 'Valgte rækker:',
11813 totalItems: 'Rækker totalt:',
11814 size: 'Side størrelse:',
11815 first: 'Første side',
11816 next: 'Næste side',
11817 previous: 'Forrige side',
11818 last: 'Sidste side'
11819 },
11820 menu:{
11821 text: 'Vælg kolonner:'
11822 },
11823 column: {
11824 hide: 'Skjul kolonne'
11825 },
11826 aggregation: {
11827 count: 'samlede rækker: ',
11828 sum: 'smalede: ',
11829 avg: 'gns: ',
11830 min: 'min: ',
11831 max: 'max: '
11832 },
11833 gridMenu: {
11834 columns: 'Columns:',
11835 importerTitle: 'Import file',
11836 exporterAllAsCsv: 'Export all data as csv',
11837 exporterVisibleAsCsv: 'Export visible data as csv',
11838 exporterSelectedAsCsv: 'Export selected data as csv',
11839 exporterAllAsPdf: 'Export all data as pdf',
11840 exporterVisibleAsPdf: 'Export visible data as pdf',
11841 exporterSelectedAsPdf: 'Export selected data as pdf',
11842 clearAllFilters: 'Clear all filters'
11843 },
11844 importer: {
11845 noHeaders: 'Column names were unable to be derived, does the file have a header?',
11846 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
11847 invalidCsv: 'File was unable to be processed, is it valid CSV?',
11848 invalidJson: 'File was unable to be processed, is it valid Json?',
11849 jsonNotArray: 'Imported json file must contain an array, aborting.'
11850 }
11851 });
11852 return $delegate;
11853 }]);
11854 }]);
12303 (function() {
12304 angular.module('ui.grid').config(['$provide', function($provide) {
12305 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12306 $delegate.add('da', {
12307 aggregate: {
12308 label: 'artikler'
12309 },
12310 groupPanel: {
12311 description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
12312 },
12313 search: {
12314 placeholder: 'Søg...',
12315 showingItems: 'Viste rækker:',
12316 selectedItems: 'Valgte rækker:',
12317 totalItems: 'Rækker totalt:',
12318 size: 'Side størrelse:',
12319 first: 'Første side',
12320 next: 'Næste side',
12321 previous: 'Forrige side',
12322 last: 'Sidste side'
12323 },
12324 menu: {
12325 text: 'Vælg kolonner:'
12326 },
12327 sort: {
12328 ascending: 'Sorter stigende',
12329 descending: 'Sorter faldende',
12330 none: 'Sorter ingen',
12331 remove: 'Fjern sortering'
12332 },
12333 column: {
12334 hide: 'Skjul kolonne'
12335 },
12336 aggregation: {
12337 count: 'antal rækker: ',
12338 sum: 'sum: ',
12339 avg: 'gns: ',
12340 min: 'min: ',
12341 max: 'max: '
12342 },
12343 pinning: {
12344 pinLeft: 'Fastgør til venstre',
12345 pinRight: 'Fastgør til højre',
12346 unpin: 'Frigør'
12347 },
12348 gridMenu: {
12349 columns: 'Kolonner:',
12350 importerTitle: 'Importer fil',
12351 exporterAllAsCsv: 'Eksporter alle data som csv',
12352 exporterVisibleAsCsv: 'Eksporter synlige data som csv',
12353 exporterSelectedAsCsv: 'Eksporter markerede data som csv',
12354 exporterAllAsPdf: 'Eksporter alle data som pdf',
12355 exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
12356 exporterSelectedAsPdf: 'Eksporter markerede data som pdf',
12357 exporterAllAsExcel: 'Eksporter alle data som excel',
12358 exporterVisibleAsExcel: 'Eksporter synlige data som excel',
12359 exporterSelectedAsExcel: 'Eksporter markerede data som excel',
12360 clearAllFilters: 'Clear all filters'
12361 },
12362 importer: {
12363 noHeaders: 'Column names were unable to be derived, does the file have a header?',
12364 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12365 invalidCsv: 'File was unable to be processed, is it valid CSV?',
12366 invalidJson: 'File was unable to be processed, is it valid Json?',
12367 jsonNotArray: 'Imported json file must contain an array, aborting.'
12368 },
12369 pagination: {
12370 aria: {
12371 pageToFirst: 'Gå til første',
12372 pageBack: 'Gå tilbage',
12373 pageSelected: 'Valgte side',
12374 pageForward: 'Gå frem',
12375 pageToLast: 'Gå til sidste'
12376 },
12377 sizes: 'genstande per side',
12378 totalItems: 'genstande',
12379 through: 'gennem',
12380 of: 'af'
12381 }
12382 });
12383 return $delegate;
12384 }]);
12385 }]);
1185512386 })();
1185612387
1185712388 (function () {
1185812389 angular.module('ui.grid').config(['$provide', function ($provide) {
1185912390 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
1186012391 $delegate.add('de', {
12392 headerCell: {
12393 aria: {
12394 defaultFilterLabel: 'Filter für Spalte',
12395 removeFilter: 'Filter löschen',
12396 columnMenuButtonLabel: 'Spaltenmenü',
12397 column: 'Spalte'
12398 },
12399 priority: 'Priorität:',
12400 filterLabel: "Filter für Spalte: "
12401 },
1186112402 aggregate: {
1186212403 label: 'Eintrag'
1186312404 },
1186512406 description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
1186612407 },
1186712408 search: {
12409 aria: {
12410 selected: 'Zeile markiert',
12411 notSelected: 'Zeile nicht markiert'
12412 },
1186812413 placeholder: 'Suche...',
1186912414 showingItems: 'Zeige Einträge:',
1187012415 selectedItems: 'Ausgewählte Einträge:',
1188112426 sort: {
1188212427 ascending: 'aufsteigend sortieren',
1188312428 descending: 'absteigend sortieren',
12429 none: 'keine Sortierung',
1188412430 remove: 'Sortierung zurücksetzen'
1188512431 },
1188612432 column: {
1189812444 pinRight: 'Rechts anheften',
1189912445 unpin: 'Lösen'
1190012446 },
12447 columnMenu: {
12448 close: 'Schließen'
12449 },
1190112450 gridMenu: {
12451 aria: {
12452 buttonLabel: 'Tabellenmenü'
12453 },
1190212454 columns: 'Spalten:',
1190312455 importerTitle: 'Datei importieren',
1190412456 exporterAllAsCsv: 'Alle Daten als CSV exportieren',
11905 exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
11906 exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
12457 exporterVisibleAsCsv: 'Sichtbare Daten als CSV exportieren',
12458 exporterSelectedAsCsv: 'Markierte Daten als CSV exportieren',
1190712459 exporterAllAsPdf: 'Alle Daten als PDF exportieren',
11908 exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
11909 exporterSelectedAsPdf: 'markierte Daten als CSV exportieren',
11910 clearAllFilters: 'Alle filter reinigen'
12460 exporterVisibleAsPdf: 'Sichtbare Daten als PDF exportieren',
12461 exporterSelectedAsPdf: 'Markierte Daten als PDF exportieren',
12462 exporterAllAsExcel: 'Alle Daten als Excel exportieren',
12463 exporterVisibleAsExcel: 'Sichtbare Daten als Excel exportieren',
12464 exporterSelectedAsExcel: 'Markierte Daten als Excel exportieren',
12465 clearAllFilters: 'Alle Filter zurücksetzen'
1191112466 },
1191212467 importer: {
1191312468 noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
1191712472 jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
1191812473 },
1191912474 pagination: {
11920 sizes: 'Einträge pro Seite',
11921 totalItems: 'Einträge'
12475 aria: {
12476 pageToFirst: 'Zum Anfang',
12477 pageBack: 'Seite zurück',
12478 pageSelected: 'Ausgewählte Seite',
12479 pageForward: 'Seite vor',
12480 pageToLast: 'Zum Ende'
12481 },
12482 sizes: 'Einträge pro Seite',
12483 totalItems: 'Einträgen',
12484 through: 'bis',
12485 of: 'von'
1192212486 },
1192312487 grouping: {
1192412488 group: 'Gruppieren',
1194412508 aria: {
1194512509 defaultFilterLabel: 'Filter for column',
1194612510 removeFilter: 'Remove Filter',
11947 columnMenuButtonLabel: 'Column Menu'
12511 columnMenuButtonLabel: 'Column Menu',
12512 column: 'Column'
1194812513 },
1194912514 priority: 'Priority:',
1195012515 filterLabel: "Filter for column: "
1195612521 description: 'Drag a column header here and drop it to group by that column.'
1195712522 },
1195812523 search: {
12524 aria: {
12525 selected: 'Row selected',
12526 notSelected: 'Row not selected'
12527 },
1195912528 placeholder: 'Search...',
1196012529 showingItems: 'Showing Items:',
1196112530 selectedItems: 'Selected Items:',
1200512574 exporterAllAsPdf: 'Export all data as pdf',
1200612575 exporterVisibleAsPdf: 'Export visible data as pdf',
1200712576 exporterSelectedAsPdf: 'Export selected data as pdf',
12577 exporterAllAsExcel: 'Export all data as excel',
12578 exporterVisibleAsExcel: 'Export visible data as excel',
12579 exporterSelectedAsExcel: 'Export selected data as excel',
1200812580 clearAllFilters: 'Clear all filters'
1200912581 },
1201012582 importer: {
1203612608 aggregate_min: 'Agg: Min',
1203712609 aggregate_avg: 'Agg: Avg',
1203812610 aggregate_remove: 'Agg: Remove'
12611 },
12612 validate: {
12613 error: 'Error:',
12614 minLength: 'Value should be at least THRESHOLD characters long.',
12615 maxLength: 'Value should be at most THRESHOLD characters long.',
12616 required: 'A value is needed.'
12617 }
12618 });
12619 return $delegate;
12620 }]);
12621 }]);
12622 })();
12623
12624 (function () {
12625 angular.module('ui.grid').config(['$provide', function($provide) {
12626 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12627 $delegate.add('es-ct', {
12628 headerCell: {
12629 aria: {
12630 defaultFilterLabel: 'Filtre per columna',
12631 removeFilter: 'Elimina el filtre',
12632 columnMenuButtonLabel: 'Menú de Columna',
12633 column: 'Columna'
12634 },
12635 priority: 'Priority:',
12636 filterLabel: 'Filtre per columna: '
12637 },
12638 aggregate: {
12639 label: 'items'
12640 },
12641 groupPanel: {
12642 description: 'Arrossegueu una capçalera de columna aquí i deixeu-lo anar per agrupar per aquesta columna.'
12643 },
12644 search: {
12645 aria: {
12646 selected: 'Fila seleccionada',
12647 notSelected: 'Fila no seleccionada'
12648 },
12649 placeholder: 'Cerca...',
12650 showingItems: 'Ítems Mostrats:',
12651 selectedItems: 'Ítems Seleccionats:',
12652 totalItems: 'Ítems Totals:',
12653 size: 'Mida de la pàgina:',
12654 first: 'Primera Pàgina',
12655 next: 'Propera Pàgina',
12656 previous: 'Pàgina Anterior',
12657 last: 'Última Pàgina'
12658 },
12659 menu: {
12660 text: 'Triar Columnes:'
12661 },
12662 sort: {
12663 ascending: 'Ordena Ascendent',
12664 descending: 'Ordena Descendent',
12665 none: 'Sense Ordre',
12666 remove: 'Eliminar Ordre'
12667 },
12668 column: {
12669 hide: 'Amaga la Columna'
12670 },
12671 aggregation: {
12672 count: 'Files Totals: ',
12673 sum: 'total: ',
12674 avg: 'mitjà: ',
12675 min: 'mín: ',
12676 max: 'màx: '
12677 },
12678 pinning: {
12679 pinLeft: "Fixar a l'Esquerra",
12680 pinRight: 'Fixar a la Dreta',
12681 unpin: 'Treure Fixació'
12682 },
12683 columnMenu: {
12684 close: 'Tanca'
12685 },
12686 gridMenu: {
12687 aria: {
12688 buttonLabel: 'Menú de Quadrícula'
12689 },
12690 columns: 'Columnes:',
12691 importerTitle: 'Importa el fitxer',
12692 exporterAllAsCsv: 'Exporta tot com CSV',
12693 exporterVisibleAsCsv: 'Exporta les dades visibles com a CSV',
12694 exporterSelectedAsCsv: 'Exporta les dades seleccionades com a CSV',
12695 exporterAllAsPdf: 'Exporta tot com PDF',
12696 exporterVisibleAsPdf: 'Exporta les dades visibles com a PDF',
12697 exporterSelectedAsPdf: 'Exporta les dades seleccionades com a PDF',
12698 exporterAllAsExcel: 'Exporta tot com Excel',
12699 exporterVisibleAsExcel: 'Exporta les dades visibles com Excel',
12700 exporterSelectedAsExcel: 'Exporta les dades seleccionades com Excel',
12701 clearAllFilters: 'Netejar tots els filtres'
12702 },
12703 importer: {
12704 noHeaders: "No va ser possible derivar els noms de les columnes, té encapçalats l'arxiu?",
12705 noObjects: "No va ser possible obtenir registres, conté dades l'arxiu, a part de les capçaleres?",
12706 invalidCsv: "No va ser possible processar l'arxiu, ¿és un CSV vàlid?",
12707 invalidJson: "No va ser possible processar l'arxiu, ¿és un JSON vàlid?",
12708 jsonNotArray: 'El fitxer json importat ha de contenir una matriu, avortant.'
12709 },
12710 pagination: {
12711 aria: {
12712 pageToFirst: 'Page to first',
12713 pageBack: 'Page back',
12714 pageSelected: 'Selected page',
12715 pageForward: 'Page forward',
12716 pageToLast: 'Page to last'
12717 },
12718 sizes: 'ítems per pàgina',
12719 totalItems: 'ítems',
12720 through: 'a',
12721 of: 'de'
12722 },
12723 grouping: {
12724 group: 'Agrupar',
12725 ungroup: 'Desagrupar',
12726 aggregate_count: 'Agr: Compte',
12727 aggregate_sum: 'Agr: Sum',
12728 aggregate_max: 'Agr: Máx',
12729 aggregate_min: 'Agr: Mín',
12730 aggregate_avg: 'Agr: Mitjà',
12731 aggregate_remove: 'Agr: Treure'
12732 },
12733 validate: {
12734 error: 'Error:',
12735 minLength: 'El valor ha de tenir almenys caràcters THRESHOLD.',
12736 maxLength: 'El valor ha de tenir com a màxim caràcters THRESHOLD.',
12737 required: 'Un valor és necessari.'
1203912738 }
1204012739 });
1204112740 return $delegate;
1209612795 exporterAllAsPdf: 'Exportar todo como pdf',
1209712796 exporterVisibleAsPdf: 'Exportar vista como pdf',
1209812797 exporterSelectedAsPdf: 'Exportar selección como pdf',
12798 exporterAllAsExcel: 'Exportar todo como excel',
12799 exporterVisibleAsExcel: 'Exportar vista como excel',
12800 exporterSelectedAsExcel: 'Exportar selección como excel',
1209912801 clearAllFilters: 'Limpiar todos los filtros'
1210012802 },
1210112803 importer: {
1226712969 exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
1226812970 exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
1226912971 exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa',
12972 exporterAllAsExcel: 'Vie tiedot excel-muodossa',
12973 exporterVisibleAsExcel: 'Vie näkyvä tieto excel-muodossa',
12974 exporterSelectedAsExcel: 'Vie valittu tieto excel-muodossa',
1227012975 clearAllFilters: 'Puhdista kaikki suodattimet'
1227112976 },
1227212977 importer: {
1228612991 angular.module('ui.grid').config(['$provide', function($provide) {
1228712992 $provide.decorator('i18nService', ['$delegate', function($delegate) {
1228812993 $delegate.add('fr', {
12994 headerCell: {
12995 aria: {
12996 defaultFilterLabel: 'Filtre de la colonne',
12997 removeFilter: 'Supprimer le filtre',
12998 columnMenuButtonLabel: 'Menu de la colonne'
12999 },
13000 priority: 'Priorité:',
13001 filterLabel: "Filtre de la colonne: "
13002 },
1228913003 aggregate: {
1229013004 label: 'éléments'
1229113005 },
1230913023 sort: {
1231013024 ascending: 'Trier par ordre croissant',
1231113025 descending: 'Trier par ordre décroissant',
13026 none: 'Aucun tri',
1231213027 remove: 'Enlever le tri'
1231313028 },
1231413029 column: {
1232613041 pinRight: 'Épingler à droite',
1232713042 unpin: 'Détacher'
1232813043 },
13044 columnMenu: {
13045 close: 'Fermer'
13046 },
1232913047 gridMenu: {
13048 aria: {
13049 buttonLabel: 'Menu du tableau'
13050 },
1233013051 columns: 'Colonnes:',
1233113052 importerTitle: 'Importer un fichier',
1233213053 exporterAllAsCsv: 'Exporter toutes les données en CSV',
1233513056 exporterAllAsPdf: 'Exporter toutes les données en PDF',
1233613057 exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
1233713058 exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF',
13059 exporterAllAsExcel: 'Exporter toutes les données en Excel',
13060 exporterVisibleAsExcel: 'Exporter les données visibles en Excel',
13061 exporterSelectedAsExcel: 'Exporter les données sélectionnées en Excel',
1233813062 clearAllFilters: 'Nettoyez tous les filtres'
1233913063 },
1234013064 importer: {
1234513069 jsonNotArray: 'Le fichier JSON importé doit contenir un tableau, abandon.'
1234613070 },
1234713071 pagination: {
13072 aria: {
13073 pageToFirst: 'Aller à la première page',
13074 pageBack: 'Page précédente',
13075 pageSelected: 'Page sélectionnée',
13076 pageForward: 'Page suivante',
13077 pageToLast: 'Aller à la dernière page'
13078 },
1234813079 sizes: 'éléments par page',
1234913080 totalItems: 'éléments',
13081 through: 'à',
1235013082 of: 'sur'
1235113083 },
1235213084 grouping: {
1235313085 group: 'Grouper',
1235413086 ungroup: 'Dégrouper',
12355 aggregate_count: 'Agg: Compte',
13087 aggregate_count: 'Agg: Compter',
1235613088 aggregate_sum: 'Agg: Somme',
1235713089 aggregate_max: 'Agg: Max',
1235813090 aggregate_min: 'Agg: Min',
1235913091 aggregate_avg: 'Agg: Moy',
1236013092 aggregate_remove: 'Agg: Retirer'
13093 },
13094 validate: {
13095 error: 'Erreur:',
13096 minLength: 'La valeur doit être supérieure ou égale à THRESHOLD caractères.',
13097 maxLength: 'La valeur doit être inférieure ou égale à THRESHOLD caractères.',
13098 required: 'Une valeur est nécéssaire.'
1236113099 }
1236213100 });
1236313101 return $delegate;
1241313151 exporterAllAsPdf: 'Export all data as pdf',
1241413152 exporterVisibleAsPdf: 'Export visible data as pdf',
1241513153 exporterSelectedAsPdf: 'Export selected data as pdf',
13154 exporterAllAsExcel: 'Export all data as excel',
13155 exporterVisibleAsExcel: 'Export visible data as excel',
13156 exporterSelectedAsExcel: 'Export selected data as excel',
1241613157 clearAllFilters: 'Clean all filters'
1241713158 },
1241813159 importer: {
1248113222 exporterAllAsPdf: 'Արտահանել PDF',
1248213223 exporterVisibleAsPdf: 'Արտահանել երևացող տվյալները PDF',
1248313224 exporterSelectedAsPdf: 'Արտահանել ընտրված տվյալները PDF',
13225 exporterAllAsExcel: 'Արտահանել excel',
13226 exporterVisibleAsExcel: 'Արտահանել երևացող տվյալները excel',
13227 exporterSelectedAsExcel: 'Արտահանել ընտրված տվյալները excel',
1248413228 clearAllFilters: 'Մաքրել բոլոր ֆիլտրերը'
1248513229 },
1248613230 importer: {
1248913233 invalidCsv: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր CSV է։',
1249013234 invalidJson: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր Json է։',
1249113235 jsonNotArray: 'Ներմուծված json ֆայլը պետք է պարունակի զանգված, կասեցվում է։'
13236 }
13237 });
13238 return $delegate;
13239 }]);
13240 }]);
13241 })();
13242
13243 (function () {
13244 angular.module('ui.grid').config(['$provide', function($provide) {
13245 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13246 $delegate.add('is', {
13247 headerCell: {
13248 aria: {
13249 defaultFilterLabel: 'Sía fyrir dálk',
13250 removeFilter: 'Fjarlægja síu',
13251 columnMenuButtonLabel: 'Dálkavalmynd'
13252 },
13253 priority: 'Forgangsröðun:',
13254 filterLabel: "Sía fyrir dálka: "
13255 },
13256 aggregate: {
13257 label: 'hlutir'
13258 },
13259 groupPanel: {
13260 description: 'Dragðu dálkhaus hingað til að flokka saman eftir þeim dálki.'
13261 },
13262 search: {
13263 placeholder: 'Leita...',
13264 showingItems: 'Sýni hluti:',
13265 selectedItems: 'Valdir hlutir:',
13266 totalItems: 'Hlutir alls:',
13267 size: 'Stærð síðu:',
13268 first: 'Fyrsta síða',
13269 next: 'Næsta síða',
13270 previous: 'Fyrri síða',
13271 last: 'Síðasta síða'
13272 },
13273 menu: {
13274 text: 'Veldu dálka:'
13275 },
13276 sort: {
13277 ascending: 'Raða hækkandi',
13278 descending: 'Raða lækkandi',
13279 none: 'Engin röðun',
13280 remove: 'Fjarlægja röðun'
13281 },
13282 column: {
13283 hide: 'Fela dálk'
13284 },
13285 aggregation: {
13286 count: 'fjöldi raða: ',
13287 sum: 'summa: ',
13288 avg: 'meðaltal: ',
13289 min: 'lágmark: ',
13290 max: 'hámark: '
13291 },
13292 pinning: {
13293 pinLeft: 'Festa til vinstri',
13294 pinRight: 'Festa til hægri',
13295 unpin: 'Losa'
13296 },
13297 columnMenu: {
13298 close: 'Loka'
13299 },
13300 gridMenu: {
13301 aria: {
13302 buttonLabel: 'Töflu valmynd'
13303 },
13304 columns: 'Dálkar:',
13305 importerTitle: 'Flytja inn skjal',
13306 exporterAllAsCsv: 'Flytja út gögn sem csv',
13307 exporterVisibleAsCsv: 'Flytja út sýnileg gögn sem csv',
13308 exporterSelectedAsCsv: 'Flytja út valin gögn sem csv',
13309 exporterAllAsPdf: 'Flytja út öll gögn sem pdf',
13310 exporterVisibleAsPdf: 'Flytja út sýnileg gögn sem pdf',
13311 exporterSelectedAsPdf: 'Flytja út valin gögn sem pdf',
13312 clearAllFilters: 'Hreinsa allar síur'
13313 },
13314 importer: {
13315 noHeaders: 'Ekki hægt að vinna dálkanöfn úr skjalinu, er skjalið örugglega með haus?',
13316 noObjects: 'Ekki hægt að vinna hluti úr skjalinu, voru örugglega gögn í skjalinu önnur en hausinn?',
13317 invalidCsv: 'Tókst ekki að vinna skjal, er það örggulega gilt CSV?',
13318 invalidJson: 'Tókst ekki að vinna skjal, er það örugglega gilt Json?',
13319 jsonNotArray: 'Innflutt json skjal verður að innihalda fylki, hætti við.'
13320 },
13321 pagination: {
13322 aria: {
13323 pageToFirst: 'Fletta að fyrstu',
13324 pageBack: 'Fletta til baka',
13325 pageSelected: 'Valin síða',
13326 pageForward: 'Fletta áfram',
13327 pageToLast: 'Fletta að síðustu'
13328 },
13329 sizes: 'hlutir á síðu',
13330 totalItems: 'hlutir',
13331 through: 'gegnum',
13332 of: 'af'
13333 },
13334 grouping: {
13335 group: 'Flokka',
13336 ungroup: 'Sundurliða',
13337 aggregate_count: 'Fjöldi: ',
13338 aggregate_sum: 'Summa: ',
13339 aggregate_max: 'Hámark: ',
13340 aggregate_min: 'Lágmark: ',
13341 aggregate_avg: 'Meðaltal: ',
13342 aggregate_remove: 'Fjarlægja: '
13343 },
13344 validate: {
13345 error: 'Villa:',
13346 minLength: 'Gildi ætti að vera a.m.k. THRESHOLD stafa langt.',
13347 maxLength: 'Gildi ætti að vera í mesta lagi THRESHOLD stafa langt.',
13348 required: 'Þarf að hafa gildi.'
1249213349 }
1249313350 });
1249413351 return $delegate;
1254913406 exporterAllAsPdf: 'Esporta tutti i dati in PDF',
1255013407 exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
1255113408 exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF',
13409 exporterAllAsExcel: 'Esporta tutti i dati in excel',
13410 exporterVisibleAsExcel: 'Esporta i dati visibili in excel',
13411 exporterSelectedAsExcel: 'Esporta i dati selezionati in excel',
1255213412 clearAllFilters: 'Pulire tutti i filtri'
1255313413 },
1255413414 importer: {
1255713417 invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
1255813418 invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
1255913419 jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
13420 },
13421 pagination: {
13422 aria: {
13423 pageToFirst: 'Prima',
13424 pageBack: 'Indietro',
13425 pageSelected: 'Pagina selezionata',
13426 pageForward: 'Avanti',
13427 pageToLast: 'Ultima'
13428 },
13429 sizes: 'elementi per pagina',
13430 totalItems: 'elementi',
13431 through: 'a',
13432 of: 'di'
1256013433 },
1256113434 grouping: {
1256213435 group: 'Raggruppa',
1256713440 aggregate_min: 'Agg: Minimo',
1256813441 aggregate_avg: 'Agg: Media',
1256913442 aggregate_remove: 'Agg: Rimuovi'
13443 },
13444 validate: {
13445 error: 'Errore:',
13446 minLength: 'Lunghezza minima pari a THRESHOLD caratteri.',
13447 maxLength: 'Lunghezza massima pari a THRESHOLD caratteri.',
13448 required: 'Necessario inserire un valore.'
1257013449 }
1257113450 });
1257213451 return $delegate;
1257813457 angular.module('ui.grid').config(['$provide', function($provide) {
1257913458 $provide.decorator('i18nService', ['$delegate', function($delegate) {
1258013459 $delegate.add('ja', {
13460 headerCell: {
13461 aria: {
13462 defaultFilterLabel: '列のフィルター',
13463 removeFilter: 'フィルターの解除',
13464 columnMenuButtonLabel: '列のメニュー'
13465 },
13466 priority: '優先度:',
13467 filterLabel: "列フィルター: "
13468 },
1258113469 aggregate: {
1258213470 label: '項目'
1258313471 },
1260113489 sort: {
1260213490 ascending: '昇順に並べ替え',
1260313491 descending: '降順に並べ替え',
13492 none: '並べ替え無し',
1260413493 remove: '並べ替えの解除'
1260513494 },
1260613495 column: {
1260713496 hide: '列の非表示'
1260813497 },
1260913498 aggregation: {
12610 count: '合計行数: ',
13499 count: '行数: ',
1261113500 sum: '合計: ',
1261213501 avg: '平均: ',
1261313502 min: '最小: ',
1261813507 pinRight: '右に固定',
1261913508 unpin: '固定解除'
1262013509 },
13510 columnMenu: {
13511 close: '閉じる'
13512 },
1262113513 gridMenu: {
12622 columns: '列:',
13514 aria: {
13515 buttonLabel: 'グリッドメニュー'
13516 },
13517 columns: '列の表示/非表示:',
1262313518 importerTitle: 'ファイルのインポート',
1262413519 exporterAllAsCsv: 'すべてのデータをCSV形式でエクスポート',
1262513520 exporterVisibleAsCsv: '表示中のデータをCSV形式でエクスポート',
1262713522 exporterAllAsPdf: 'すべてのデータをPDF形式でエクスポート',
1262813523 exporterVisibleAsPdf: '表示中のデータをPDF形式でエクスポート',
1262913524 exporterSelectedAsPdf: '選択したデータをPDF形式でエクスポート',
12630 clearAllFilters: 'すべてのフィルタを清掃してください'
13525 clearAllFilters: 'すべてのフィルタをクリア'
1263113526 },
1263213527 importer: {
1263313528 noHeaders: '列名を取得できません。ファイルにヘッダが含まれていることを確認してください。',
1263713532 jsonNotArray: 'インポートしたJSONファイルには配列が含まれている必要があります。処理を中止します。'
1263813533 },
1263913534 pagination: {
12640 sizes: '項目/ページ',
12641 totalItems: '項目'
13535 aria: {
13536 pageToFirst: '最初のページ',
13537 pageBack: '前のページ',
13538 pageSelected: '現在のページ',
13539 pageForward: '次のページ',
13540 pageToLast: '最後のページ'
13541 },
13542 sizes: '件/ページ',
13543 totalItems: '件',
13544 through: 'から',
13545 of: '件/全'
13546 },
13547 grouping: {
13548 group: 'グループ化',
13549 ungroup: 'グループ化の解除',
13550 aggregate_count: '集計表示: 行数',
13551 aggregate_sum: '集計表示: 合計',
13552 aggregate_max: '集計表示: 最大',
13553 aggregate_min: '集計表示: 最小',
13554 aggregate_avg: '集計表示: 平均',
13555 aggregate_remove: '集計表示: 解除'
13556 },
13557 validate: {
13558 error: 'Error:',
13559 minLength: 'THRESHOLD 文字以上で入力してください。',
13560 maxLength: 'THRESHOLD 文字以下で入力してください。',
13561 required: '値が必要です。'
1264213562 }
1264313563 });
1264413564 return $delegate;
1277113691 exporterAllAsPdf: 'Exporteer alle data als pdf',
1277213692 exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
1277313693 exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf',
13694 exporterAllAsExcel: 'Exporteer alle data als excel',
13695 exporterVisibleAsExcel: 'Exporteer zichtbare data als excel',
13696 exporterSelectedAsExcel: 'Exporteer alle data als excel',
1277413697 clearAllFilters: 'Reinig alle filters'
1277513698 },
1277613699 importer: {
1280413727 (function () {
1280513728 angular.module('ui.grid').config(['$provide', function($provide) {
1280613729 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13730 $delegate.add('no', {
13731 headerCell: {
13732 aria: {
13733 defaultFilterLabel: 'Filter for kolonne',
13734 removeFilter: 'Fjern filter',
13735 columnMenuButtonLabel: 'Kolonnemeny'
13736 },
13737 priority: 'Prioritet:',
13738 filterLabel: "Filter for kolonne: "
13739 },
13740 aggregate: {
13741 label: 'elementer'
13742 },
13743 groupPanel: {
13744 description: 'Trekk en kolonneoverskrift hit og slipp den for å gruppere etter den kolonnen.'
13745 },
13746 search: {
13747 placeholder: 'Søk...',
13748 showingItems: 'Viste elementer:',
13749 selectedItems: 'Valgte elementer:',
13750 totalItems: 'Antall elementer:',
13751 size: 'Sidestørrelse:',
13752 first: 'Første side',
13753 next: 'Neste side',
13754 previous: 'Forrige side',
13755 last: 'Siste side'
13756 },
13757 menu: {
13758 text: 'Velg kolonner:'
13759 },
13760 sort: {
13761 ascending: 'Sortere stigende',
13762 descending: 'Sortere fallende',
13763 none: 'Ingen sortering',
13764 remove: 'Fjern sortering'
13765 },
13766 column: {
13767 hide: 'Skjul kolonne'
13768 },
13769 aggregation: {
13770 count: 'antall rader: ',
13771 sum: 'total: ',
13772 avg: 'gjennomsnitt: ',
13773 min: 'minimum: ',
13774 max: 'maksimum: '
13775 },
13776 pinning: {
13777 pinLeft: 'Fest til venstre',
13778 pinRight: 'Fest til høyre',
13779 unpin: 'Løsne'
13780 },
13781 columnMenu: {
13782 close: 'Lukk'
13783 },
13784 gridMenu: {
13785 aria: {
13786 buttonLabel: 'Grid Menu'
13787 },
13788 columns: 'Kolonner:',
13789 importerTitle: 'Importer fil',
13790 exporterAllAsCsv: 'Eksporter alle data som csv',
13791 exporterVisibleAsCsv: 'Eksporter synlige data som csv',
13792 exporterSelectedAsCsv: 'Eksporter utvalgte data som csv',
13793 exporterAllAsPdf: 'Eksporter alle data som pdf',
13794 exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
13795 exporterSelectedAsPdf: 'Eksporter utvalgte data som pdf',
13796 exporterAllAsExcel: 'Eksporter alle data som excel',
13797 exporterVisibleAsExcel: 'Eksporter synlige data som excel',
13798 exporterSelectedAsExcel: 'Eksporter utvalgte data som excel',
13799 clearAllFilters: 'Clear all filters'
13800 },
13801 importer: {
13802 noHeaders: 'Kolonnenavn kunne ikke avledes. Har filen en overskrift?',
13803 noObjects: 'Objekter kunne ikke avledes. Er der andre data i filen enn overskriften?',
13804 invalidCsv: 'Filen kunne ikke behandles. Er den gyldig CSV?',
13805 invalidJson: 'Filen kunne ikke behandles. Er den gyldig JSON?',
13806 jsonNotArray: 'Importert JSON-fil må inneholde en liste. Avbryter.'
13807 },
13808 pagination: {
13809 aria: {
13810 pageToFirst: 'Gå til første side',
13811 pageBack: 'Gå til forrige side',
13812 pageSelected: 'Valgte side',
13813 pageForward: 'Gå til neste side',
13814 pageToLast: 'Gå til siste side'
13815 },
13816 sizes: 'elementer per side',
13817 totalItems: 'elementer',
13818 through: 'til',
13819 of: 'av'
13820 },
13821 grouping: {
13822 group: 'Gruppere',
13823 ungroup: 'Fjerne gruppering',
13824 aggregate_count: 'Agr: Antall',
13825 aggregate_sum: 'Agr: Sum',
13826 aggregate_max: 'Agr: Maksimum',
13827 aggregate_min: 'Agr: Minimum',
13828 aggregate_avg: 'Agr: Gjennomsnitt',
13829 aggregate_remove: 'Agr: Fjern'
13830 }
13831 });
13832 return $delegate;
13833 }]);
13834 }]);
13835 })();
13836
13837 (function () {
13838 angular.module('ui.grid').config(['$provide', function($provide) {
13839 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13840 $delegate.add('pl', {
13841 headerCell: {
13842 aria: {
13843 defaultFilterLabel: 'Filtr dla kolumny',
13844 removeFilter: 'Usuń filtr',
13845 columnMenuButtonLabel: 'Opcje kolumny',
13846 column: 'Kolumna'
13847 },
13848 priority: 'Priorytet:',
13849 filterLabel: "Filtr dla kolumny: "
13850 },
13851 aggregate: {
13852 label: 'pozycji'
13853 },
13854 groupPanel: {
13855 description: 'Przeciągnij nagłówek kolumny tutaj, aby pogrupować według niej.'
13856 },
13857 search: {
13858 aria: {
13859 selected: 'Wiersz zaznaczony',
13860 notSelected: 'Wiersz niezaznaczony'
13861 },
13862 placeholder: 'Szukaj...',
13863 showingItems: 'Widoczne pozycje:',
13864 selectedItems: 'Zaznaczone pozycje:',
13865 totalItems: 'Wszystkich pozycji:',
13866 size: 'Rozmiar strony:',
13867 first: 'Pierwsza strona',
13868 next: 'Następna strona',
13869 previous: 'Poprzednia strona',
13870 last: 'Ostatnia strona'
13871 },
13872 menu: {
13873 text: 'Wybierz kolumny:'
13874 },
13875 sort: {
13876 ascending: 'Sortuj rosnąco',
13877 descending: 'Sortuj malejąco',
13878 none: 'Brak sortowania',
13879 remove: 'Wyłącz sortowanie'
13880 },
13881 column: {
13882 hide: 'Ukryj kolumnę'
13883 },
13884 aggregation: {
13885 count: 'Razem pozycji: ',
13886 sum: 'Razem: ',
13887 avg: 'Średnia: ',
13888 min: 'Min: ',
13889 max: 'Max: '
13890 },
13891 pinning: {
13892 pinLeft: 'Przypnij do lewej',
13893 pinRight: 'Przypnij do prawej',
13894 unpin: 'Odepnij'
13895 },
13896 columnMenu: {
13897 close: 'Zamknij'
13898 },
13899 gridMenu: {
13900 aria: {
13901 buttonLabel: 'Opcje tabeli'
13902 },
13903 columns: 'Kolumny:',
13904 importerTitle: 'Importuj plik',
13905 exporterAllAsCsv: 'Eksportuj wszystkie dane do csv',
13906 exporterVisibleAsCsv: 'Eksportuj widoczne dane do csv',
13907 exporterSelectedAsCsv: 'Eksportuj zaznaczone dane do csv',
13908 exporterAllAsPdf: 'Eksportuj wszystkie dane do pdf',
13909 exporterVisibleAsPdf: 'Eksportuj widoczne dane do pdf',
13910 exporterSelectedAsPdf: 'Eksportuj zaznaczone dane do pdf',
13911 exporterAllAsExcel: 'Eksportuj wszystkie dane do excel',
13912 exporterVisibleAsExcel: 'Eksportuj widoczne dane do excel',
13913 exporterSelectedAsExcel: 'Eksportuj zaznaczone dane do excel',
13914 clearAllFilters: 'Wyczyść filtry'
13915 },
13916 importer: {
13917 noHeaders: 'Nie udało się wczytać nazw kolumn. Czy plik posiada nagłówek?',
13918 noObjects: 'Nie udalo się wczytać pozycji. Czy plik zawiera dane?',
13919 invalidCsv: 'Nie udało się przetworzyć pliku. Czy to prawidłowy plik CSV?',
13920 invalidJson: 'Nie udało się przetworzyć pliku. Czy to prawidłowy plik JSON?',
13921 jsonNotArray: 'Importowany plik JSON musi zawierać tablicę. Importowanie przerwane.'
13922 },
13923 pagination: {
13924 aria: {
13925 pageToFirst: 'Pierwsza strona',
13926 pageBack: 'Poprzednia strona',
13927 pageSelected: 'Wybrana strona',
13928 pageForward: 'Następna strona',
13929 pageToLast: 'Ostatnia strona'
13930 },
13931 sizes: 'pozycji na stronę',
13932 totalItems: 'pozycji',
13933 through: 'do',
13934 of: 'z'
13935 },
13936 grouping: {
13937 group: 'Grupuj',
13938 ungroup: 'Rozgrupuj',
13939 aggregate_count: 'Zbiorczo: Razem',
13940 aggregate_sum: 'Zbiorczo: Suma',
13941 aggregate_max: 'Zbiorczo: Max',
13942 aggregate_min: 'Zbiorczo: Min',
13943 aggregate_avg: 'Zbiorczo: Średnia',
13944 aggregate_remove: 'Zbiorczo: Usuń'
13945 },
13946 validate: {
13947 error: 'Błąd:',
13948 minLength: 'Wartość powinna składać się z co najmniej THRESHOLD znaków.',
13949 maxLength: 'Wartość powinna składać się z przynajmniej THRESHOLD znaków.',
13950 required: 'Wartość jest wymagana.'
13951 }
13952 });
13953 return $delegate;
13954 }]);
13955 }]);
13956 })();
13957
13958 (function () {
13959 angular.module('ui.grid').config(['$provide', function($provide) {
13960 $provide.decorator('i18nService', ['$delegate', function($delegate) {
1280713961 $delegate.add('pt-br', {
13962 headerCell: {
13963 aria: {
13964 defaultFilterLabel: 'Filtro por coluna',
13965 removeFilter: 'Remover filtro',
13966 columnMenuButtonLabel: 'Menu coluna',
13967 column: 'Coluna'
13968 },
13969 priority: 'Prioridade:',
13970 filterLabel: "Filtro por coluna: "
13971 },
1280813972 aggregate: {
1280913973 label: 'itens'
1281013974 },
1281213976 description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
1281313977 },
1281413978 search: {
13979 aria: {
13980 selected: 'Linha selecionada',
13981 notSelected: 'Linha não está selecionada'
13982 },
1281513983 placeholder: 'Procurar...',
1281613984 showingItems: 'Mostrando os Itens:',
1281713985 selectedItems: 'Items Selecionados:',
1282813996 sort: {
1282913997 ascending: 'Ordenar Ascendente',
1283013998 descending: 'Ordenar Descendente',
13999 none: 'Nenhuma Ordem',
1283114000 remove: 'Remover Ordenação'
1283214001 },
1283314002 column: {
1284514014 pinRight: 'Fixar Direita',
1284614015 unpin: 'Desprender'
1284714016 },
14017 columnMenu: {
14018 close: 'Fechar'
14019 },
1284814020 gridMenu: {
14021 aria: {
14022 buttonLabel: 'Menu Grid'
14023 },
1284914024 columns: 'Colunas:',
1285014025 importerTitle: 'Importar arquivo',
1285114026 exporterAllAsCsv: 'Exportar todos os dados como csv',
1285414029 exporterAllAsPdf: 'Exportar todos os dados como pdf',
1285514030 exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
1285614031 exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
14032 exporterAllAsExcel: 'Exportar todos os dados como excel',
14033 exporterVisibleAsExcel: 'Exportar dados visíveis como excel',
14034 exporterSelectedAsExcel: 'Exportar dados selecionados como excel',
1285714035 clearAllFilters: 'Limpar todos os filtros'
1285814036 },
1285914037 importer: {
1286414042 jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
1286514043 },
1286614044 pagination: {
14045 aria: {
14046 pageToFirst: 'Primeira página',
14047 pageBack: 'Página anterior',
14048 pageSelected: 'Página Selecionada',
14049 pageForward: 'Proxima',
14050 pageToLast: 'Anterior'
14051 },
1286714052 sizes: 'itens por página',
12868 totalItems: 'itens'
14053 totalItems: 'itens',
14054 through: 'através dos',
14055 of: 'de'
1286914056 },
1287014057 grouping: {
1287114058 group: 'Agrupar',
1287614063 aggregate_min: 'Agr: Min',
1287714064 aggregate_avg: 'Agr: Med',
1287814065 aggregate_remove: 'Agr: Remover'
14066 },
14067 validate: {
14068 error: 'Erro:',
14069 minLength: 'O valor deve ter, no minimo, THRESHOLD caracteres.',
14070 maxLength: 'O valor deve ter, no máximo, THRESHOLD caracteres.',
14071 required: 'Um valor é necessário.'
1287914072 }
1288014073 });
1288114074 return $delegate;
1288714080 angular.module('ui.grid').config(['$provide', function($provide) {
1288814081 $provide.decorator('i18nService', ['$delegate', function($delegate) {
1288914082 $delegate.add('pt', {
14083 headerCell: {
14084 aria: {
14085 defaultFilterLabel: 'Filtro por coluna',
14086 removeFilter: 'Remover filtro',
14087 columnMenuButtonLabel: 'Menu coluna',
14088 column: 'Coluna'
14089 },
14090 priority: 'Prioridade:',
14091 filterLabel: "Filtro por coluna: "
14092 },
1289014093 aggregate: {
1289114094 label: 'itens'
1289214095 },
1289414097 description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
1289514098 },
1289614099 search: {
14100 aria: {
14101 selected: 'Linha selecionada',
14102 notSelected: 'Linha não está selecionada'
14103 },
1289714104 placeholder: 'Procurar...',
1289814105 showingItems: 'Mostrando os Itens:',
1289914106 selectedItems: 'Itens Selecionados:',
1291014117 sort: {
1291114118 ascending: 'Ordenar Ascendente',
1291214119 descending: 'Ordenar Descendente',
14120 none: 'Nenhuma Ordem',
1291314121 remove: 'Remover Ordenação'
1291414122 },
1291514123 column: {
1292714135 pinRight: 'Fixar Direita',
1292814136 unpin: 'Desprender'
1292914137 },
14138 columnMenu: {
14139 close: 'Fechar'
14140 },
1293014141 gridMenu: {
14142 aria: {
14143 buttonLabel: 'Menu Grid'
14144 },
1293114145 columns: 'Colunas:',
1293214146 importerTitle: 'Importar ficheiro',
1293314147 exporterAllAsCsv: 'Exportar todos os dados como csv',
1293614150 exporterAllAsPdf: 'Exportar todos os dados como pdf',
1293714151 exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
1293814152 exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
14153 exporterAllAsExcel: 'Exportar todos os dados como excel',
14154 exporterVisibleAsExcel: 'Exportar dados visíveis como excel',
14155 exporterSelectedAsExcel: 'Exportar dados selecionados como excel',
1293914156 clearAllFilters: 'Limpar todos os filtros'
1294014157 },
1294114158 importer: {
1294614163 jsonNotArray: 'Ficheiro json importado tem que conter um array. Interrompendo.'
1294714164 },
1294814165 pagination: {
14166 aria: {
14167 pageToFirst: 'Primeira página',
14168 pageBack: 'Página anterior',
14169 pageSelected: 'Página Selecionada',
14170 pageForward: 'Próxima',
14171 pageToLast: 'Anterior'
14172 },
1294914173 sizes: 'itens por página',
1295014174 totalItems: 'itens',
14175 through: 'a',
1295114176 of: 'de'
1295214177 },
1295314178 grouping: {
1295914184 aggregate_min: 'Agr: Min',
1296014185 aggregate_avg: 'Agr: Med',
1296114186 aggregate_remove: 'Agr: Remover'
14187 },
14188 validate: {
14189 error: 'Erro:',
14190 minLength: 'O valor deve ter, no minimo, THRESHOLD caracteres.',
14191 maxLength: 'O valor deve ter, no máximo, THRESHOLD caracteres.',
14192 required: 'Um valor é necessário.'
1296214193 }
1296314194 });
1296414195 return $delegate;
1296914200 (function () {
1297014201 angular.module('ui.grid').config(['$provide', function($provide) {
1297114202 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14203 $delegate.add('ro', {
14204 headerCell: {
14205 aria: {
14206 defaultFilterLabel: 'Filtru pentru coloana',
14207 removeFilter: 'Sterge filtru',
14208 columnMenuButtonLabel: 'Column Menu'
14209 },
14210 priority: 'Prioritate:',
14211 filterLabel: "Filtru pentru coloana:"
14212 },
14213 aggregate: {
14214 label: 'Elemente'
14215 },
14216 groupPanel: {
14217 description: 'Trage un cap de coloana aici pentru a grupa elementele dupa coloana respectiva'
14218 },
14219 search: {
14220 placeholder: 'Cauta...',
14221 showingItems: 'Arata elementele:',
14222 selectedItems: 'Elementele selectate:',
14223 totalItems: 'Total elemente:',
14224 size: 'Marime pagina:',
14225 first: 'Prima pagina',
14226 next: 'Pagina urmatoare',
14227 previous: 'Pagina anterioara',
14228 last: 'Ultima pagina'
14229 },
14230 menu: {
14231 text: 'Alege coloane:'
14232 },
14233 sort: {
14234 ascending: 'Ordoneaza crescator',
14235 descending: 'Ordoneaza descrescator',
14236 none: 'Fara ordonare',
14237 remove: 'Sterge ordonarea'
14238 },
14239 column: {
14240 hide: 'Ascunde coloana'
14241 },
14242 aggregation: {
14243 count: 'total linii: ',
14244 sum: 'total: ',
14245 avg: 'medie: ',
14246 min: 'min: ',
14247 max: 'max: '
14248 },
14249 pinning: {
14250 pinLeft: 'Pin la stanga',
14251 pinRight: 'Pin la dreapta',
14252 unpin: 'Sterge pinul'
14253 },
14254 columnMenu: {
14255 close: 'Inchide'
14256 },
14257 gridMenu: {
14258 aria: {
14259 buttonLabel: 'Grid Menu'
14260 },
14261 columns: 'Coloane:',
14262 importerTitle: 'Incarca fisier',
14263 exporterAllAsCsv: 'Exporta toate datele ca csv',
14264 exporterVisibleAsCsv: 'Exporta datele vizibile ca csv',
14265 exporterSelectedAsCsv: 'Exporta datele selectate ca csv',
14266 exporterAllAsPdf: 'Exporta toate datele ca pdf',
14267 exporterVisibleAsPdf: 'Exporta datele vizibile ca pdf',
14268 exporterSelectedAsPdf: 'Exporta datele selectate ca csv pdf',
14269 clearAllFilters: 'Sterge toate filtrele'
14270 },
14271 importer: {
14272 noHeaders: 'Numele coloanelor nu a putut fi incarcat, acest fisier are un header?',
14273 noObjects: 'Datele nu au putut fi incarcate, exista date in fisier in afara numelor de coloane?',
14274 invalidCsv: 'Fisierul nu a putut fi procesat, ati incarcat un CSV valid ?',
14275 invalidJson: 'Fisierul nu a putut fi procesat, ati incarcat un Json valid?',
14276 jsonNotArray: 'Json-ul incarcat trebuie sa contina un array, inchidere.'
14277 },
14278 pagination: {
14279 aria: {
14280 pageToFirst: 'Prima pagina',
14281 pageBack: 'O pagina inapoi',
14282 pageSelected: 'Pagina selectata',
14283 pageForward: 'O pagina inainte',
14284 pageToLast: 'Ultima pagina'
14285 },
14286 sizes: 'Elemente per pagina',
14287 totalItems: 'elemente',
14288 through: 'prin',
14289 of: 'of'
14290 },
14291 grouping: {
14292 group: 'Grupeaza',
14293 ungroup: 'Opreste gruparea',
14294 aggregate_count: 'Agg: Count',
14295 aggregate_sum: 'Agg: Sum',
14296 aggregate_max: 'Agg: Max',
14297 aggregate_min: 'Agg: Min',
14298 aggregate_avg: 'Agg: Avg',
14299 aggregate_remove: 'Agg: Remove'
14300 }
14301 });
14302 return $delegate;
14303 }]);
14304 }]);
14305 })();
14306
14307 (function() {
14308 angular.module('ui.grid').config(['$provide', function($provide) {
14309 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14310 $delegate.add('rs-lat', {
14311 headerCell: {
14312 aria: {
14313 defaultFilterLabel: 'Filter za kolonu',
14314 removeFilter: 'Ukloni Filter',
14315 columnMenuButtonLabel: 'Meni Kolone',
14316 column: 'Kolona'
14317 },
14318 priority: 'Prioritet:',
14319 filterLabel: "Filter za kolonu: "
14320 },
14321 aggregate: {
14322 label: 'stavke'
14323 },
14324 groupPanel: {
14325 description: 'Ovde prevuci zaglavlje kolone i spusti do grupe pored te kolone.'
14326 },
14327 search: {
14328 aria: {
14329 selected: 'Red odabran',
14330 notSelected: 'Red nije odabran'
14331 },
14332 placeholder: 'Pretraga...',
14333 showingItems: 'Prikazane Stavke:',
14334 selectedItems: 'Odabrane Stavke:',
14335 totalItems: 'Ukupno Stavki:',
14336 size: 'Veličina Stranice:',
14337 first: 'Prva Stranica',
14338 next: 'Sledeća Stranica',
14339 previous: 'Prethodna Stranica',
14340 last: 'Poslednja Stranica'
14341 },
14342 menu: {
14343 text: 'Odaberite kolonu:'
14344 },
14345 sort: {
14346 ascending: 'Sortiraj po rastućem redosledu',
14347 descending: 'Sortiraj po opadajućem redosledu',
14348 none: 'Bez Sortiranja',
14349 remove: 'Ukloni Sortiranje'
14350 },
14351 column: {
14352 hide: 'Sakrij Kolonu'
14353 },
14354 aggregation: {
14355 count: 'ukupno redova: ',
14356 sum: 'ukupno: ',
14357 avg: 'prosecno: ',
14358 min: 'minimum: ',
14359 max: 'maksimum: '
14360 },
14361 pinning: {
14362 pinLeft: 'Zakači Levo',
14363 pinRight: 'Zakači Desno',
14364 unpin: 'Otkači'
14365 },
14366 columnMenu: {
14367 close: 'Zatvori'
14368 },
14369 gridMenu: {
14370 aria: {
14371 buttonLabel: 'Rešetkasti Meni'
14372 },
14373 columns: 'Kolone:',
14374 importerTitle: 'Importuj fajl',
14375 exporterAllAsCsv: 'Eksportuj sve podatke kao csv',
14376 exporterVisibleAsCsv: 'Eksportuj vidljive podatke kao csv',
14377 exporterSelectedAsCsv: 'Eksportuj obeležene podatke kao csv',
14378 exporterAllAsPdf: 'Eksportuj sve podatke kao pdf',
14379 exporterVisibleAsPdf: 'Eksportuj vidljive podake kao pdf',
14380 exporterSelectedAsPdf: 'Eksportuj odabrane podatke kao pdf',
14381 exporterAllAsExcel: 'Eksportuj sve podatke kao excel',
14382 exporterVisibleAsExcel: 'Eksportuj vidljive podatke kao excel',
14383 exporterSelectedAsExcel: 'Eksportuj odabrane podatke kao excel',
14384 clearAllFilters: 'Obriši sve filtere'
14385 },
14386 importer: {
14387 noHeaders: 'Kolone se nisu mogle podeliti, da li fajl poseduje heder?',
14388 noObjects: 'Objecti nisu mogli biti podeljeni, da li je bilo i drugih podataka sem hedera?',
14389 invalidCsv: 'Fajl nije bilo moguće procesirati, da li je ispravni CSV?',
14390 invalidJson: 'Fajl nije bilo moguće procesirati, da li je ispravni JSON',
14391 jsonNotArray: 'Importovani json fajl mora da sadrži niz, prekidam operaciju.'
14392 },
14393 pagination: {
14394 aria: {
14395 pageToFirst: 'Prva stranica',
14396 pageBack: 'Stranica pre',
14397 pageSelected: 'Odabrana stranica',
14398 pageForward: 'Sledeća stranica',
14399 pageToLast: 'Poslednja stranica'
14400 },
14401 sizes: 'stavki po stranici',
14402 totalItems: 'stavke',
14403 through: 'kroz',
14404 of: 'od'
14405 },
14406 grouping: {
14407 group: 'Grupiši',
14408 ungroup: 'Odrupiši',
14409 aggregate_count: 'Agg: Broj',
14410 aggregate_sum: 'Agg: Suma',
14411 aggregate_max: 'Agg: Maksimum',
14412 aggregate_min: 'Agg: Minimum',
14413 aggregate_avg: 'Agg: Prosečna',
14414 aggregate_remove: 'Agg: Ukloni'
14415 },
14416 validate: {
14417 error: 'Greška:',
14418 minLength: 'Vrednost bi trebala da bude duga bar THRESHOLD karaktera.',
14419 maxLength: 'Vrednost bi trebalo da bude najviše duga THRESHOLD karaktera.',
14420 required: 'Portreba je vrednost.'
14421 }
14422 });
14423 return $delegate;
14424 }]);
14425 }]);
14426 })();
14427
14428 (function () {
14429 angular.module('ui.grid').config(['$provide', function($provide) {
14430 $provide.decorator('i18nService', ['$delegate', function($delegate) {
1297214431 $delegate.add('ru', {
14432 headerCell: {
14433 aria: {
14434 defaultFilterLabel: 'Фильтр столбца',
14435 removeFilter: 'Удалить фильтр',
14436 columnMenuButtonLabel: 'Меню столбца'
14437 },
14438 priority: 'Приоритет:',
14439 filterLabel: "Фильтр столбца: "
14440 },
1297314441 aggregate: {
1297414442 label: 'элементы'
1297514443 },
1299314461 sort: {
1299414462 ascending: 'По возрастанию',
1299514463 descending: 'По убыванию',
14464 none: 'Без сортировки',
1299614465 remove: 'Убрать сортировку'
1299714466 },
1299814467 column: {
1300514474 min: 'мин: ',
1300614475 max: 'макс: '
1300714476 },
13008 pinning: {
13009 pinLeft: 'Закрепить слева',
13010 pinRight: 'Закрепить справа',
13011 unpin: 'Открепить'
14477 pinning: {
14478 pinLeft: 'Закрепить слева',
14479 pinRight: 'Закрепить справа',
14480 unpin: 'Открепить'
14481 },
14482 columnMenu: {
14483 close: 'Закрыть'
1301214484 },
1301314485 gridMenu: {
14486 aria: {
14487 buttonLabel: 'Меню'
14488 },
1301414489 columns: 'Столбцы:',
13015 importerTitle: 'Import file',
14490 importerTitle: 'Импортировать файл',
1301614491 exporterAllAsCsv: 'Экспортировать всё в CSV',
1301714492 exporterVisibleAsCsv: 'Экспортировать видимые данные в CSV',
1301814493 exporterSelectedAsCsv: 'Экспортировать выбранные данные в CSV',
1302214497 clearAllFilters: 'Очистите все фильтры'
1302314498 },
1302414499 importer: {
13025 noHeaders: 'Column names were unable to be derived, does the file have a header?',
13026 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13027 invalidCsv: 'File was unable to be processed, is it valid CSV?',
13028 invalidJson: 'File was unable to be processed, is it valid Json?',
13029 jsonNotArray: 'Imported json file must contain an array, aborting.'
14500 noHeaders: 'Не удалось получить названия столбцов, есть ли в файле заголовок?',
14501 noObjects: 'Не удалось получить данные, есть ли в файле строки кроме заголовка?',
14502 invalidCsv: 'Не удалось обработать файл, это правильный CSV-файл?',
14503 invalidJson: 'Не удалось обработать файл, это правильный JSON?',
14504 jsonNotArray: 'Импортируемый JSON-файл должен содержать массив, операция отменена.'
14505 },
14506 pagination: {
14507 aria: {
14508 pageToFirst: 'Первая страница',
14509 pageBack: 'Предыдущая страница',
14510 pageSelected: 'Выбранная страница',
14511 pageForward: 'Следующая страница',
14512 pageToLast: 'Последняя страница'
14513 },
14514 sizes: 'строк на страницу',
14515 totalItems: 'строк',
14516 through: 'по',
14517 of: 'из'
14518 },
14519 grouping: {
14520 group: 'Группировать',
14521 ungroup: 'Разгруппировать',
14522 aggregate_count: 'Группировать: Count',
14523 aggregate_sum: 'Для группы: Сумма',
14524 aggregate_max: 'Для группы: Максимум',
14525 aggregate_min: 'Для группы: Минимум',
14526 aggregate_avg: 'Для группы: Среднее',
14527 aggregate_remove: 'Для группы: Пусто'
1303014528 }
1303114529 });
1303214530 return $delegate;
1303414532 }]);
1303514533 })();
1303614534
13037 (function () {
13038 angular.module('ui.grid').config(['$provide', function($provide) {
13039 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13040 $delegate.add('sk', {
13041 aggregate: {
13042 label: 'items'
13043 },
13044 groupPanel: {
13045 description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
13046 },
13047 search: {
13048 placeholder: 'Hľadaj...',
13049 showingItems: 'Zobrazujem položky:',
13050 selectedItems: 'Vybraté položky:',
13051 totalItems: 'Počet položiek:',
13052 size: 'Počet:',
13053 first: 'Prvá strana',
13054 next: 'Ďalšia strana',
13055 previous: 'Predchádzajúca strana',
13056 last: 'Posledná strana'
13057 },
13058 menu: {
13059 text: 'Vyberte stĺpce:'
13060 },
13061 sort: {
13062 ascending: 'Zotriediť vzostupne',
13063 descending: 'Zotriediť zostupne',
13064 remove: 'Vymazať triedenie'
13065 },
13066 aggregation: {
13067 count: 'total rows: ',
13068 sum: 'total: ',
13069 avg: 'avg: ',
13070 min: 'min: ',
13071 max: 'max: '
13072 },
13073 gridMenu: {
13074 columns: 'Columns:',
13075 importerTitle: 'Import file',
13076 exporterAllAsCsv: 'Export all data as csv',
13077 exporterVisibleAsCsv: 'Export visible data as csv',
13078 exporterSelectedAsCsv: 'Export selected data as csv',
13079 exporterAllAsPdf: 'Export all data as pdf',
13080 exporterVisibleAsPdf: 'Export visible data as pdf',
13081 exporterSelectedAsPdf: 'Export selected data as pdf',
13082 clearAllFilters: 'Clear all filters'
13083 },
13084 importer: {
13085 noHeaders: 'Column names were unable to be derived, does the file have a header?',
13086 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13087 invalidCsv: 'File was unable to be processed, is it valid CSV?',
13088 invalidJson: 'File was unable to be processed, is it valid Json?',
13089 jsonNotArray: 'Imported json file must contain an array, aborting.'
13090 }
13091 });
13092 return $delegate;
13093 }]);
13094 }]);
14535 (function() {
14536 angular.module('ui.grid').config(['$provide', function($provide) {
14537 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14538 $delegate.add('sk', {
14539 headerCell: {
14540 aria: {
14541 defaultFilterLabel: 'Filter pre stĺpec',
14542 removeFilter: 'Odstrániť filter',
14543 columnMenuButtonLabel: 'Menu pre stĺpec',
14544 column: 'Stĺpec'
14545 },
14546 priority: 'Priorita:',
14547 filterLabel: "Filter pre stĺpec: "
14548 },
14549 aggregate: {
14550 label: 'položky'
14551 },
14552 groupPanel: {
14553 description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
14554 },
14555 search: {
14556 aria: {
14557 selected: 'Označený riadok',
14558 notSelected: 'Neoznačený riadok'
14559 },
14560 placeholder: 'Hľadaj...',
14561 showingItems: 'Zobrazujem položky:',
14562 selectedItems: 'Vybraté položky:',
14563 totalItems: 'Počet položiek:',
14564 size: 'Počet:',
14565 first: 'Prvá strana',
14566 next: 'Ďalšia strana',
14567 previous: 'Predchádzajúca strana',
14568 last: 'Posledná strana'
14569 },
14570 menu: {
14571 text: 'Vyberte stĺpce:'
14572 },
14573 sort: {
14574 ascending: 'Zotriediť vzostupne',
14575 descending: 'Zotriediť zostupne',
14576 none: 'Nezotriediť',
14577 remove: 'Vymazať triedenie'
14578 },
14579 column: {
14580 hide: 'Skryť stĺpec'
14581 },
14582 aggregation: {
14583 count: 'počet riadkov: ',
14584 sum: 'spolu: ',
14585 avg: 'avg: ',
14586 min: 'min: ',
14587 max: 'max: '
14588 },
14589 pinning: {
14590 pinLeft: 'Pripnúť vľavo',
14591 pinRight: 'Pripnúť vpravo',
14592 unpin: 'Odopnúť'
14593 },
14594 columnMenu: {
14595 close: 'Zavrieť'
14596 },
14597 gridMenu: {
14598 aria: {
14599 buttonLabel: 'Grid Menu'
14600 },
14601 columns: 'Stĺpce:',
14602 importerTitle: 'Importovať súbor',
14603 exporterAllAsCsv: 'Exportovať všetky údaje ako CSV',
14604 exporterVisibleAsCsv: 'Exportovť viditeľné údaje ako CSV',
14605 exporterSelectedAsCsv: 'Exportovať označené údaje ako CSV',
14606 exporterAllAsPdf: 'Exportovať všetky údaje ako pdf',
14607 exporterVisibleAsPdf: 'Exportovať viditeľné údaje ako pdf',
14608 exporterSelectedAsPdf: 'Exportovať označené údaje ako pdf',
14609 exporterAllAsExcel: 'Exportovať všetky údaje ako excel',
14610 exporterVisibleAsExcel: 'Exportovať viditeľné údaje ako excel',
14611 exporterSelectedAsExcel: 'Exportovať označené údaje ako excel',
14612 clearAllFilters: 'Zrušiť všetky filtre'
14613 },
14614 importer: {
14615 noHeaders: 'Názvy stĺpcov sa nedali odvodiť, má súbor hlavičku?',
14616 noObjects: 'Objekty nebolo možné odvodiť, existovali iné údaje v súbore ako hlavičky?',
14617 invalidCsv: 'Súbor sa nepodarilo spracovať, je to platný súbor CSV?',
14618 invalidJson: 'Súbor nebolo možné spracovať, je to platný súbor typu Json?',
14619 jsonNotArray: 'Importovaný súbor json musí obsahovať pole, ukončujem.'
14620 },
14621 pagination: {
14622 aria: {
14623 pageToFirst: 'Strana na začiatok',
14624 pageBack: 'Strana dozadu',
14625 pageSelected: 'Označená strana',
14626 pageForward: 'Strana dopredu',
14627 pageToLast: 'Strana na koniec'
14628 },
14629 sizes: 'položky na stranu',
14630 totalItems: 'položky spolu',
14631 through: 'do konca',
14632 of: 'z'
14633 },
14634 grouping: {
14635 group: 'Zoskupiť',
14636 ungroup: 'Zrušiť zoskupenie',
14637 aggregate_count: 'Agg: Počet',
14638
14639 aggregate_sum: 'Agg: Suma',
14640 aggregate_max: 'Agg: Max',
14641 aggregate_min: 'Agg: Min',
14642 aggregate_avg: 'Agg: Avg',
14643 aggregate_remove: 'Agg: Zrušiť'
14644 },
14645 validate: {
14646 error: 'Chyba:',
14647 minLength: 'Hodnota by mala mať aspoň THRESHOLD znakov dlhá.',
14648 maxLength: 'Hodnota by mala byť maximálne THRESHOLD znakov dlhá.',
14649 required: 'Vyžaduje sa hodnota.'
14650 }
14651 });
14652 return $delegate;
14653 }]);
14654 }]);
1309514655 })();
1309614656
1309714657 (function () {
1322414784 importer: {
1322514785 noHeaders: 'பத்தியின் தலைப்புகளை பெற இயலவில்லை, கோப்பிற்கு தலைப்பு உள்ளதா?',
1322614786 noObjects: 'இலக்குகளை உருவாக்க முடியவில்லை, கோப்பில் தலைப்புகளை தவிர தரவு ஏதேனும் உள்ளதா? ',
13227 invalidCsv: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
14787 invalidCsv: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
1322814788 invalidJson: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - json',
1322914789 jsonNotArray: 'படித்த கோப்பில் வரிசைகள் உள்ளது, நடைமுறை ரத்து செய் : json'
1323014790 },
1323114791 pagination: {
13232 sizes : 'உருப்படிகள் / பக்கம்',
13233 totalItems : 'உருப்படிகள் '
14792 sizes : 'உருப்படிகள் / பக்கம்',
14793 totalItems : 'உருப்படிகள் '
1323414794 },
1323514795 grouping: {
13236 group : 'குழு',
14796 group : 'குழு',
1323714797 ungroup : 'பிரி',
13238 aggregate_count : 'மதிப்பீட்டு : எண்ணு',
14798 aggregate_count : 'மதிப்பீட்டு : எண்ணு',
1323914799 aggregate_sum : 'மதிப்பீட்டு : கூட்டல்',
13240 aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
13241 aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
13242 aggregate_avg : 'மதிப்பீட்டு : சராசரி',
14800 aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
14801 aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
14802 aggregate_avg : 'மதிப்பீட்டு : சராசரி',
1324314803 aggregate_remove : 'மதிப்பீட்டு : நீக்கு'
14804 }
14805 });
14806 return $delegate;
14807 }]);
14808 }]);
14809 })();
14810
14811 (function() {
14812 angular.module('ui.grid').config(['$provide', function($provide) {
14813 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14814 $delegate.add('tr', {
14815 headerCell: {
14816 aria: {
14817 defaultFilterLabel: 'Sütun için filtre',
14818 removeFilter: 'Filtreyi Kaldır',
14819 columnMenuButtonLabel: 'Sütun Menüsü'
14820 },
14821 priority: 'Öncelik:',
14822 filterLabel: "Sütun için filtre: "
14823 },
14824 aggregate: {
14825 label: 'kayıtlar'
14826 },
14827 groupPanel: {
14828 description: 'Sütuna göre gruplamak için sütun başlığını buraya sürükleyin ve bırakın.'
14829 },
14830 search: {
14831 placeholder: 'Arama...',
14832 showingItems: 'Gösterilen Kayıt:',
14833 selectedItems: 'Seçili Kayıt:',
14834 totalItems: 'Toplam Kayıt:',
14835 size: 'Sayfa Boyutu:',
14836 first: 'İlk Sayfa',
14837 next: 'Sonraki Sayfa',
14838 previous: 'Önceki Sayfa',
14839 last: 'Son Sayfa'
14840 },
14841 menu: {
14842 text: 'Sütunları Seç:'
14843 },
14844 sort: {
14845 ascending: 'Artan Sırada Sırala',
14846 descending: 'Azalan Sırada Sırala',
14847 none: 'Sıralama Yapma',
14848 remove: 'Sıralamayı Kaldır'
14849 },
14850 column: {
14851 hide: 'Sütunu Gizle'
14852 },
14853 aggregation: {
14854 count: 'toplam satır: ',
14855 sum: 'toplam: ',
14856 avg: 'ort: ',
14857 min: 'min: ',
14858 max: 'maks: '
14859 },
14860 pinning: {
14861 pinLeft: 'Sola Sabitle',
14862 pinRight: 'Sağa Sabitle',
14863 unpin: 'Sabitlemeyi Kaldır'
14864 },
14865 columnMenu: {
14866 close: 'Kapat'
14867 },
14868 gridMenu: {
14869 aria: {
14870 buttonLabel: 'Tablo Menü'
14871 },
14872 columns: 'Sütunlar:',
14873 importerTitle: 'Dosya içeri aktar',
14874 exporterAllAsCsv: 'Bütün veriyi CSV olarak dışarı aktar',
14875 exporterVisibleAsCsv: 'Görünen veriyi CSV olarak dışarı aktar',
14876 exporterSelectedAsCsv: 'Seçili veriyi CSV olarak dışarı aktar',
14877 exporterAllAsPdf: 'Bütün veriyi PDF olarak dışarı aktar',
14878 exporterVisibleAsPdf: 'Görünen veriyi PDF olarak dışarı aktar',
14879 exporterSelectedAsPdf: 'Seçili veriyi PDF olarak dışarı aktar',
14880 clearAllFilters: 'Bütün filtreleri kaldır'
14881 },
14882 importer: {
14883 noHeaders: 'Sütun isimleri üretilemiyor, dosyanın bir başlığı var mı?',
14884 noObjects: 'Nesneler üretilemiyor, dosyada başlıktan başka bir veri var mı?',
14885 invalidCsv: 'Dosya işlenemedi, geçerli bir CSV dosyası mı?',
14886 invalidJson: 'Dosya işlenemedi, geçerli bir Json dosyası mı?',
14887 jsonNotArray: 'Alınan Json dosyasında bir dizi bulunmalıdır, işlem iptal ediliyor.'
14888 },
14889 pagination: {
14890 aria: {
14891 pageToFirst: 'İlk sayfaya',
14892 pageBack: 'Geri git',
14893 pageSelected: 'Seçili sayfa',
14894 pageForward: 'İleri git',
14895 pageToLast: 'Sona git'
14896 },
14897 sizes: 'Sayfadaki nesne sayısı',
14898 totalItems: 'kayıtlar',
14899 through: '', // note(fsw) : turkish dont have this preposition
14900 of: '' // note(fsw) : turkish dont have this preposition
14901 },
14902 grouping: {
14903 group: 'Grupla',
14904 ungroup: 'Gruplama',
14905 aggregate_count: 'Yekun: Sayı',
14906 aggregate_sum: 'Yekun: Toplam',
14907 aggregate_max: 'Yekun: Maks',
14908 aggregate_min: 'Yekun: Min',
14909 aggregate_avg: 'Yekun: Ort',
14910 aggregate_remove: 'Yekun: Sil'
14911 }
14912 });
14913 return $delegate;
14914 }]);
14915 }]);
14916 })();
14917
14918 (function () {
14919 angular.module('ui.grid').config(['$provide', function($provide) {
14920 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14921 $delegate.add('ua', {
14922 headerCell: {
14923 aria: {
14924 defaultFilterLabel: 'Фільтр стовпчика',
14925 removeFilter: 'Видалити фільтр',
14926 columnMenuButtonLabel: 'Меню ствпчика'
14927 },
14928 priority: 'Пріоритет:',
14929 filterLabel: "Фільтр стовпчика: "
14930 },
14931 aggregate: {
14932 label: 'елементи'
14933 },
14934 groupPanel: {
14935 description: 'Для групування за стовпчиком перетягніть сюди його назву.'
14936 },
14937 search: {
14938 placeholder: 'Пошук...',
14939 showingItems: 'Показати елементи:',
14940 selectedItems: 'Обрані елементи:',
14941 totalItems: 'Усього елементів:',
14942 size: 'Розмір сторінки:',
14943 first: 'Перша сторінка',
14944 next: 'Наступна сторінка',
14945 previous: 'Попередня сторінка',
14946 last: 'Остання сторінка'
14947 },
14948 menu: {
14949 text: 'Обрати ствпчики:'
14950 },
14951 sort: {
14952 ascending: 'За зростанням',
14953 descending: 'За спаданням',
14954 none: 'Без сортування',
14955 remove: 'Прибрати сортування'
14956 },
14957 column: {
14958 hide: 'Приховати стовпчик'
14959 },
14960 aggregation: {
14961 count: 'усього рядків: ',
14962 sum: 'ітого: ',
14963 avg: 'середнє: ',
14964 min: 'мін: ',
14965 max: 'макс: '
14966 },
14967 pinning: {
14968 pinLeft: 'Закріпити ліворуч',
14969 pinRight: 'Закріпити праворуч',
14970 unpin: 'Відкріпити'
14971 },
14972 columnMenu: {
14973 close: 'Закрити'
14974 },
14975 gridMenu: {
14976 aria: {
14977 buttonLabel: 'Меню'
14978 },
14979 columns: 'Стовпчики:',
14980 importerTitle: 'Імпортувати файл',
14981 exporterAllAsCsv: 'Експортувати все в CSV',
14982 exporterVisibleAsCsv: 'Експортувати видимі дані в CSV',
14983 exporterSelectedAsCsv: 'Експортувати обрані дані в CSV',
14984 exporterAllAsPdf: 'Експортувати все в PDF',
14985 exporterVisibleAsPdf: 'Експортувати видимі дані в PDF',
14986 exporterSelectedAsPdf: 'Експортувати обрані дані в PDF',
14987 clearAllFilters: 'Очистити всі фільтри'
14988 },
14989 importer: {
14990 noHeaders: 'Не вдалося отримати назви стовпчиків, чи є в файлі заголовок?',
14991 noObjects: 'Не вдалося отримати дані, чи є в файлі рядки окрім заголовка?',
14992 invalidCsv: 'Не вдалося обробити файл, чи це коректний CSV-файл?',
14993 invalidJson: 'Не вдалося обробити файл, чи це коректний JSON?',
14994 jsonNotArray: 'JSON-файл що імпортується повинен містити масив, операцію скасовано.'
14995 },
14996 pagination: {
14997 aria: {
14998 pageToFirst: 'Перша сторінка',
14999 pageBack: 'Попередня сторінка',
15000 pageSelected: 'Обрана сторінка',
15001 pageForward: 'Наступна сторінка',
15002 pageToLast: 'Остання сторінка'
15003 },
15004 sizes: 'рядків на сторінку',
15005 totalItems: 'рядків',
15006 through: 'по',
15007 of: 'з'
15008 },
15009 grouping: {
15010 group: 'Групувати',
15011 ungroup: 'Розгрупувати',
15012 aggregate_count: 'Групувати: Кількість',
15013 aggregate_sum: 'Для групи: Сума',
15014 aggregate_max: 'Для групи: Максимум',
15015 aggregate_min: 'Для групи: Мінімум',
15016 aggregate_avg: 'Для групи: Серднє',
15017 aggregate_remove: 'Для групи: Пусто'
1324415018 }
1324515019 });
1324615020 return $delegate;
1329115065 *
1329215066 * @description Services for i18n
1329315067 */
13294 module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
13295 function ($log, i18nConstants, $rootScope) {
15068 module.service('i18nService', ['$log', '$parse', 'i18nConstants', '$rootScope',
15069 function ($log, $parse, i18nConstants, $rootScope) {
1329615070
1329715071 var langCache = {
1329815072 _langs: {},
1329915073 current: null,
15074 fallback: i18nConstants.DEFAULT_LANG,
1330015075 get: function (lang) {
13301 return this._langs[lang.toLowerCase()];
15076 var self = this,
15077 fallbackLang = self.getFallbackLang();
15078
15079 if (lang !== self.fallback) {
15080 return angular.extend({}, self._langs[fallbackLang],
15081 self._langs[lang.toLowerCase()]);
15082 }
15083
15084 return self._langs[lang.toLowerCase()];
1330215085 },
1330315086 add: function (lang, strings) {
1330415087 var lower = lang.toLowerCase();
1330515088 if (!this._langs[lower]) {
1330615089 this._langs[lower] = {};
1330715090 }
13308 angular.extend(this._langs[lower], strings);
15091 angular.merge(this._langs[lower], strings);
1330915092 },
1331015093 getAllLangs: function () {
1331115094 var langs = [];
1332215105 setCurrent: function (lang) {
1332315106 this.current = lang.toLowerCase();
1332415107 },
15108 setFallback: function (lang) {
15109 this.fallback = lang.toLowerCase();
15110 },
1332515111 getCurrentLang: function () {
1332615112 return this.current;
15113 },
15114 getFallbackLang: function () {
15115 return this.fallback.toLowerCase();
1332715116 }
1332815117 };
1332915118
1333515124 * @methodOf ui.grid.i18n.service:i18nService
1333615125 * @description Adds the languages and strings to the cache. Decorate this service to
1333715126 * add more translation strings
13338 * @param {string} lang language to add
15127 * @param {string} langs languages to add
1333915128 * @param {object} stringMaps of strings to add grouped by property names
1334015129 * @example
1334115130 * <pre>
1339215181 * @name getSafeText
1339315182 * @methodOf ui.grid.i18n.service:i18nService
1339415183 * @description returns the text specified in the path or a Missing text if text is not found
13395 * @param {string} path property path to use for retrieving text from string map
13396 * @param {string} lang to return. If not specified, returns current language
15184 * @param {String} path property path to use for retrieving text from string map
15185 * @param {String} [lang] to return. If not specified, returns current language
1339715186 * @returns {object} the translation for the path
1339815187 * @example
1339915188 * <pre>
1340115190 * </pre>
1340215191 */
1340315192 getSafeText: function (path, lang) {
13404 var language = lang ? lang : service.getCurrentLang();
13405 var trans = langCache.get(language);
15193 var language = lang || service.getCurrentLang(),
15194 trans = langCache.get(language),
15195 missing = i18nConstants.MISSING + path,
15196 getter = $parse(path);
1340615197
1340715198 if (!trans) {
13408 return i18nConstants.MISSING;
13409 }
13410
13411 var paths = path.split('.');
13412 var current = trans;
13413
13414 for (var i = 0; i < paths.length; ++i) {
13415 if (current[paths[i]] === undefined || current[paths[i]] === null) {
13416 return i18nConstants.MISSING;
13417 } else {
13418 current = current[paths[i]];
13419 }
13420 }
13421
13422 return current;
13423
15199 return missing;
15200 }
15201
15202 return getter(trans) || missing;
1342415203 },
1342515204
1342615205 /**
1342815207 * @name setCurrentLang
1342915208 * @methodOf ui.grid.i18n.service:i18nService
1343015209 * @description sets the current language to use in the application
13431 * $broadcasts the Update_Event on the $rootScope
15210 * $broadcasts the i18nConstants.UPDATE_EVENT on the $rootScope
1343215211 * @param {string} lang to set
1343315212 * @example
1343415213 * <pre>
1343515214 * i18nService.setCurrentLang('fr');
1343615215 * </pre>
1343715216 */
13438
1343915217 setCurrentLang: function (lang) {
1344015218 if (lang) {
1344115219 langCache.setCurrent(lang);
1344215220 $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
15221 }
15222 },
15223
15224 /**
15225 * @ngdoc service
15226 * @name setFallbackLang
15227 * @methodOf ui.grid.i18n.service:i18nService
15228 * @description sets the fallback language to use in the application.
15229 * The default fallback language is english.
15230 * @param {string} lang to set
15231 * @example
15232 * <pre>
15233 * i18nService.setFallbackLang('en');
15234 * </pre>
15235 */
15236 setFallbackLang: function (lang) {
15237 if (lang) {
15238 langCache.setFallback(lang);
1344315239 }
1344415240 },
1344515241
1345615252 langCache.setCurrent(lang);
1345715253 }
1345815254 return lang;
13459 }
13460
15255 },
15256
15257 /**
15258 * @ngdoc service
15259 * @name getFallbackLang
15260 * @methodOf ui.grid.i18n.service:i18nService
15261 * @description returns the fallback language used in the application
15262 */
15263 getFallbackLang: function () {
15264 return langCache.getFallbackLang();
15265 }
1346115266 };
1346215267
1346315268 return service;
13464
1346515269 }]);
1346615270
13467 var localeDirective = function (i18nService, i18nConstants) {
15271 function localeDirective(i18nService, i18nConstants) {
1346815272 return {
1346915273 compile: function () {
1347015274 return {
1348515289 };
1348615290 }
1348715291 };
13488 };
15292 }
1348915293
1349015294 module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
1349115295
1349215296 // directive syntax
13493 var uitDirective = function ($parse, i18nService, i18nConstants) {
15297 function uitDirective(i18nService, i18nConstants) {
1349415298 return {
1349515299 restrict: 'EA',
1349615300 compile: function () {
1349715301 return {
1349815302 pre: function ($scope, $elm, $attrs) {
13499 var alias1 = DIRECTIVE_ALIASES[0],
13500 alias2 = DIRECTIVE_ALIASES[1];
13501 var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
13502 var missing = i18nConstants.MISSING + token;
13503 var observer;
15303 var listener, observer, prop,
15304 alias1 = DIRECTIVE_ALIASES[0],
15305 alias2 = DIRECTIVE_ALIASES[1],
15306 token = $attrs[alias1] || $attrs[alias2] || $elm.html();
15307
15308 function translateToken(property) {
15309 var safeText = i18nService.getSafeText(property);
15310
15311 $elm.html(safeText);
15312 }
15313
1350415314 if ($attrs.$$observers) {
13505 var prop = $attrs[alias1] ? alias1 : alias2;
15315 prop = $attrs[alias1] ? alias1 : alias2;
1350615316 observer = $attrs.$observe(prop, function (result) {
1350715317 if (result) {
13508 $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
15318 translateToken(result);
1350915319 }
1351015320 });
1351115321 }
13512 var getter = $parse(token);
13513 var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
15322
15323 listener = $scope.$on(i18nConstants.UPDATE_EVENT, function() {
1351415324 if (observer) {
1351515325 observer($attrs[alias1] || $attrs[alias2]);
1351615326 } else {
1351715327 // set text based on i18n current language
13518 $elm.html(getter(i18nService.get()) || missing);
15328 translateToken(token);
1351915329 }
1352015330 });
1352115331 $scope.$on('$destroy', listener);
1352215332
13523 $elm.html(getter(i18nService.get()) || missing);
15333 translateToken(token);
1352415334 }
1352515335 };
1352615336 }
1352715337 };
13528 };
13529
13530 angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
13531 module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
13532 } );
15338 }
15339
15340 angular.forEach(DIRECTIVE_ALIASES, function ( alias ) {
15341 module.directive(alias, ['i18nService', 'i18nConstants', uitDirective]);
15342 });
1353315343
1353415344 // optional filter syntax
13535 var uitFilter = function ($parse, i18nService, i18nConstants) {
13536 return function (data) {
13537 var getter = $parse(data);
15345 function uitFilter(i18nService) {
15346 return function (data, lang) {
1353815347 // set text based on i18n current language
13539 return getter(i18nService.get()) || i18nConstants.MISSING + data;
15348 return i18nService.getSafeText(data, lang);
1354015349 };
13541 };
13542
13543 angular.forEach( FILTER_ALIASES, function ( alias ) {
13544 module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
13545 } );
13546
13547
15350 }
15351
15352 angular.forEach(FILTER_ALIASES, function ( alias ) {
15353 module.filter(alias, ['i18nService', uitFilter]);
15354 });
1354815355 })();
15356
1354915357 (function() {
1355015358 angular.module('ui.grid').config(['$provide', function($provide) {
1355115359 $provide.decorator('i18nService', ['$delegate', function($delegate) {
1374115549 */
1374215550 var module = angular.module('ui.grid.autoResize', ['ui.grid']);
1374315551
13744
13745 module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
15552 /**
15553 * @ngdoc directive
15554 * @name ui.grid.autoResize.directive:uiGridAutoResize
15555 * @element div
15556 * @restrict A
15557 *
15558 * @description Stacks on top of the ui-grid directive and
15559 * adds the a watch to the grid's height and width which refreshes
15560 * the grid content whenever its dimensions change.
15561 *
15562 */
15563 module.directive('uiGridAutoResize', ['gridUtil', function(gridUtil) {
1374615564 return {
1374715565 require: 'uiGrid',
1374815566 scope: false,
13749 link: function ($scope, $elm, $attrs, uiGridCtrl) {
13750 var prevGridWidth, prevGridHeight;
15567 link: function($scope, $elm, $attrs, uiGridCtrl) {
15568 var debouncedRefresh;
1375115569
1375215570 function getDimensions() {
13753 prevGridHeight = gridUtil.elementHeight($elm);
13754 prevGridWidth = gridUtil.elementWidth($elm);
13755 }
13756
13757 // Initialize the dimensions
13758 getDimensions();
13759
13760 var resizeTimeoutId;
13761 function startTimeout() {
13762 clearTimeout(resizeTimeoutId);
13763
13764 resizeTimeoutId = setTimeout(function () {
13765 var newGridHeight = gridUtil.elementHeight($elm);
13766 var newGridWidth = gridUtil.elementWidth($elm);
13767
13768 if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
13769 uiGridCtrl.grid.gridHeight = newGridHeight;
13770 uiGridCtrl.grid.gridWidth = newGridWidth;
13771
13772 $scope.$apply(function () {
13773 uiGridCtrl.grid.refresh()
13774 .then(function () {
13775 getDimensions();
13776
13777 startTimeout();
13778 });
15571 return {
15572 width: gridUtil.elementWidth($elm),
15573 height: gridUtil.elementHeight($elm)
15574 };
15575 }
15576
15577 function refreshGrid(prevWidth, prevHeight, width, height) {
15578 if ($elm[0].offsetParent !== null) {
15579 uiGridCtrl.grid.gridWidth = width;
15580 uiGridCtrl.grid.gridHeight = height;
15581 uiGridCtrl.grid.queueGridRefresh()
15582 .then(function() {
15583 uiGridCtrl.grid.api.core.raise.gridDimensionChanged(prevHeight, prevWidth, height, width);
1377915584 });
13780 }
13781 else {
13782 startTimeout();
13783 }
13784 }, 250);
13785 }
13786
13787 startTimeout();
13788
13789 $scope.$on('$destroy', function() {
13790 clearTimeout(resizeTimeoutId);
15585 }
15586 }
15587
15588 debouncedRefresh = gridUtil.debounce(refreshGrid, 400);
15589
15590 $scope.$watchCollection(getDimensions, function(newValues, oldValues) {
15591 if (!angular.equals(newValues, oldValues)) {
15592 debouncedRefresh(oldValues.width, oldValues.height, newValues.width, newValues.height);
15593 }
1379115594 });
1379215595 }
1379315596 };
1380715610
1380815611 <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
1380915612
13810 This module provides auto-resizing functionality to UI-Grid.
15613 This module provides cell navigation functionality to UI-Grid.
1381115614 */
1381215615 var module = angular.module('ui.grid.cellNav', ['ui.grid']);
1381315616
1389515698 case uiGridCellNavConstants.direction.PG_DOWN:
1389615699 return this.getRowColPageDown(curRow, curCol);
1389715700 }
13898
1389915701 };
1390015702
1390115703 UiGridCellNav.prototype.initializeSelection = function () {
1390515707 return null;
1390615708 }
1390715709
13908 var curRowIndex = 0;
13909 var curColIndex = 0;
13910 return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
15710 return new GridRowColumn(focusableRows[0], focusableCols[0]); // return same row
1391115711 };
1391215712
1391315713 UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
1391615716 var curColIndex = focusableCols.indexOf(curCol);
1391715717 var curRowIndex = focusableRows.indexOf(curRow);
1391815718
13919 //could not find column in focusable Columns so set it to 1
15719 // could not find column in focusable Columns so set it to 1
1392015720 if (curColIndex === -1) {
1392115721 curColIndex = 1;
1392215722 }
1392315723
1392415724 var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
1392515725
13926 //get column to left
13927 if (nextColIndex > curColIndex) {
15726 // get column to left
15727 if (nextColIndex >= curColIndex) {
1392815728 // On the first row
1392915729 // if (curRowIndex === 0 && curColIndex === 0) {
1393015730 // return null;
1393115731 // }
1393215732 if (curRowIndex === 0) {
13933 return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
15733 return new GridRowColumn(curRow, focusableCols[nextColIndex]); // return same row
1393415734 }
1393515735 else {
13936 //up one row and far right column
15736 // up one row and far right column
1393715737 return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
1393815738 }
1393915739 }
1395015750 var curColIndex = focusableCols.indexOf(curCol);
1395115751 var curRowIndex = focusableRows.indexOf(curRow);
1395215752
13953 //could not find column in focusable Columns so set it to 0
15753 // could not find column in focusable Columns so set it to 0
1395415754 if (curColIndex === -1) {
1395515755 curColIndex = 0;
1395615756 }
1395715757 var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
1395815758
13959 if (nextColIndex < curColIndex) {
15759 if (nextColIndex <= curColIndex) {
1396015760 if (curRowIndex === focusableRows.length - 1) {
13961 return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
15761 return new GridRowColumn(curRow, focusableCols[nextColIndex]); // return same row
1396215762 }
1396315763 else {
13964 //down one row and far left column
15764 // down one row and far left column
1396515765 return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
1396615766 }
1396715767 }
1397615776 var curColIndex = focusableCols.indexOf(curCol);
1397715777 var curRowIndex = focusableRows.indexOf(curRow);
1397815778
13979 //could not find column in focusable Columns so set it to 0
15779 // could not find column in focusable Columns so set it to 0
1398015780 if (curColIndex === -1) {
1398115781 curColIndex = 0;
1398215782 }
1398315783
1398415784 if (curRowIndex === focusableRows.length - 1) {
13985 return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
15785 return new GridRowColumn(curRow, focusableCols[curColIndex]); // return same row
1398615786 }
1398715787 else {
13988 //down one row
15788 // down one row
1398915789 return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
1399015790 }
1399115791 };
1399615796 var curColIndex = focusableCols.indexOf(curCol);
1399715797 var curRowIndex = focusableRows.indexOf(curRow);
1399815798
13999 //could not find column in focusable Columns so set it to 0
15799 // could not find column in focusable Columns so set it to 0
1400015800 if (curColIndex === -1) {
1400115801 curColIndex = 0;
1400215802 }
1400315803
1400415804 var pageSize = this.bodyContainer.minRowsToRender();
1400515805 if (curRowIndex >= focusableRows.length - pageSize) {
14006 return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
15806 return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); // return last row
1400715807 }
1400815808 else {
14009 //down one page
15809 // down one page
1401015810 return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
1401115811 }
1401215812 };
1401715817 var curColIndex = focusableCols.indexOf(curCol);
1401815818 var curRowIndex = focusableRows.indexOf(curRow);
1401915819
14020 //could not find column in focusable Columns so set it to 0
15820 // could not find column in focusable Columns so set it to 0
1402115821 if (curColIndex === -1) {
1402215822 curColIndex = 0;
1402315823 }
1402415824
1402515825 if (curRowIndex === 0) {
14026 return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
15826 return new GridRowColumn(curRow, focusableCols[curColIndex]); // return same row
1402715827 }
1402815828 else {
14029 //up one row
15829 // up one row
1403015830 return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
1403115831 }
1403215832 };
1403715837 var curColIndex = focusableCols.indexOf(curCol);
1403815838 var curRowIndex = focusableRows.indexOf(curRow);
1403915839
14040 //could not find column in focusable Columns so set it to 0
15840 // could not find column in focusable Columns so set it to 0
1404115841 if (curColIndex === -1) {
1404215842 curColIndex = 0;
1404315843 }
1404415844
1404515845 var pageSize = this.bodyContainer.minRowsToRender();
1404615846 if (curRowIndex - pageSize < 0) {
14047 return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
15847 return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); // return first row
1404815848 }
1404915849 else {
14050 //up one page
15850 // up one page
1405115851 return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
1405215852 }
1405315853 };
1407115871
1407215872
1407315873 /**
14074 * @ngdoc object
14075 * @name ui.grid.cellNav:Grid.cellNav
15874 * @ngdoc object
15875 * @name ui.grid.cellNav.Grid:cellNav
1407615876 * @description cellNav properties added to grid class
1407715877 */
1407815878 grid.cellNav = {};
1409615896 * @eventOf ui.grid.cellNav.api:PublicApi
1409715897 * @description raised when the active cell is changed
1409815898 * <pre>
14099 * gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
15899 * gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol) {})
1410015900 * </pre>
1410115901 * @param {object} newRowCol new position
1410215902 * @param {object} oldRowCol old position
1417615976 * @param {object} rowCol the rowCol to evaluate
1417715977 */
1417815978 rowColSelectIndex: function (rowCol) {
14179 //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
15979 // return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
1418015980 var index = -1;
1418115981 for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
1418215982 if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
1419415994 grid.api.registerEventsFromObject(publicApi.events);
1419515995
1419615996 grid.api.registerMethodsFromObject(publicApi.methods);
14197
1419815997 },
1419915998
1420015999 defaultGridOptions: function (gridOptions) {
1421416013 * <br/>Defaults to false
1421516014 */
1421616015 gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
16016
16017 /**
16018 * @ngdoc array
16019 * @name keyDownOverrides
16020 * @propertyOf ui.grid.cellNav.api:GridOptions
16021 * @description An array of event objects to override on keydown. If an event is overridden, the viewPortKeyDown event will
16022 * be raised with the overridden events, allowing custom keydown behavior.
16023 * <br/>Defaults to []
16024 */
16025 gridOptions.keyDownOverrides = gridOptions.keyDownOverrides || [];
1421716026
1421816027 },
1421916028
1426016069 return uiGridCellNavConstants.direction.UP;
1426116070 }
1426216071
14263 if (evt.keyCode === uiGridConstants.keymap.PG_UP){
16072 if (evt.keyCode === uiGridConstants.keymap.PG_UP) {
1426416073 return uiGridCellNavConstants.direction.PG_UP;
1426516074 }
1426616075
1426916078 return uiGridCellNavConstants.direction.DOWN;
1427016079 }
1427116080
14272 if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
16081 if (evt.keyCode === uiGridConstants.keymap.PG_DOWN) {
1427316082 return uiGridCellNavConstants.direction.PG_DOWN;
1427416083 }
1427516084
1433316142
1433416143 // Broadcast the navigation
1433516144 if (gridRow !== null && gridCol !== null) {
14336 grid.cellNav.broadcastCellNav(rowCol);
16145 grid.cellNav.broadcastCellNav(rowCol, null, null);
1433716146 }
1433816147 });
14339
14340
14341
1434216148 },
1434316149
1434416150
1435416160 * we include (thisColIndex / totalNumberCols) % of this column width
1435516161 * @param {Grid} grid the grid you'd like to act upon, usually available
1435616162 * from gridApi.grid
14357 * @param {gridCol} upToCol the column to total up to and including
16163 * @param {GridColumn} upToCol the column to total up to and including
1435816164 */
1435916165 getLeftWidth: function (grid, upToCol) {
1436016166 var width = 0;
1436716173
1436816174 // total column widths up-to but not including the passed in column
1436916175 grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
14370 if ( index < lastIndex ){
16176 if ( index < lastIndex ) {
1437116177 width += col.drawnWidth;
1437216178 }
1437316179 });
1441516221 </file>
1441616222 </example>
1441716223 */
14418 module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile',
14419 function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile) {
16224 module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile', 'i18nService',
16225 function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile, i18nService) {
1442016226 return {
1442116227 replace: true,
1442216228 priority: -150,
1443316239
1443416240 uiGridCtrl.cellNav = {};
1443516241
14436 //Ensure that the object has all of the methods we expect it to
16242 // Ensure that the object has all of the methods we expect it to
1443716243 uiGridCtrl.cellNav.makeRowCol = function (obj) {
1443816244 if (!(obj instanceof GridRowColumn)) {
1443916245 obj = new GridRowColumn(obj.row, obj.col);
1444316249
1444416250 uiGridCtrl.cellNav.getActiveCell = function () {
1444516251 var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
14446 if (elms.length > 0){
16252 if (elms.length > 0) {
1444716253 return elms[0];
1444816254 }
1444916255
1447416280
1447516281 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
1447616282
14477 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1) {
16283 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1 || (grid.cellNav.lastRowCol.col === col && grid.cellNav.lastRowCol.row === row)) {
1447816284 var newRowCol = new GridRowColumn(row, col);
1447916285
14480 grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
14481 grid.cellNav.lastRowCol = newRowCol;
16286 if (grid.cellNav.lastRowCol === null || grid.cellNav.lastRowCol.row !== newRowCol.row || grid.cellNav.lastRowCol.col !== newRowCol.col || grid.options.enableCellEditOnFocus) {
16287 grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol, originEvt);
16288 grid.cellNav.lastRowCol = newRowCol;
16289 }
1448216290 if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
1448316291 grid.cellNav.focusedCells.push(rowCol);
1448416292 } else {
1454116349
1454216350 // Scroll to the new cell, if it's not completely visible within the render container's viewport
1454316351 grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
14544 uiGridCtrl.cellNav.broadcastCellNav(rowCol);
16352 uiGridCtrl.cellNav.broadcastCellNav(rowCol, null, evt);
1454516353 });
1454616354
1454716355
1455316361 };
1455416362 },
1455516363 post: function ($scope, $elm, $attrs, uiGridCtrl) {
14556 var _scope = $scope;
1455716364 var grid = uiGridCtrl.grid;
14558
14559 function addAriaLiveRegion(){
16365 var usesAria = true;
16366
16367 // Detect whether we are using ngAria
16368 // (if ngAria module is not used then the stuff inside addAriaLiveRegion
16369 // is not used and provides extra fluff)
16370 try {
16371 angular.module('ngAria');
16372 }
16373 catch (err) {
16374 usesAria = false;
16375 }
16376
16377 function addAriaLiveRegion() {
1456016378 // Thanks to google docs for the inspiration behind how to do this
1456116379 // XXX: Why is this entire mess nessasary?
1456216380 // Because browsers take a lot of coercing to get them to read out live regions
14563 //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
16381 // http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
1456416382 var ariaNotifierDomElt = '<div ' +
1456516383 'id="' + grid.id +'-aria-speakable" ' +
1456616384 'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
1456716385 'aria-live="assertive" ' +
14568 'role="region" ' +
16386 'role="alert" ' +
1456916387 'aria-atomic="true" ' +
1457016388 'aria-hidden="false" ' +
1457116389 'aria-relevant="additions" ' +
1458416402 * use the alert to notify the user of the movement.
1458516403 * In all other cases we do want a notification event.
1458616404 */
14587 if (originEvt && originEvt.type === 'focus'){return;}
14588
14589 function setNotifyText(text){
14590 if (text === ariaNotifier.text()){return;}
16405 if (originEvt && originEvt.type === 'focus') {return;}
16406
16407 function setNotifyText(text) {
16408 if (text === ariaNotifier.text().trim()) {return;}
1459116409 ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
1459216410 /*
1459316411 * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
1459516413 ariaNotifier[0].innerHTML = "";
1459616414 ariaNotifier[0].style.visibility = 'hidden';
1459716415 ariaNotifier[0].style.visibility = 'visible';
14598 if (text !== ''){
16416 if (text !== '') {
1459916417 ariaNotifier[0].style.clip = 'auto';
1460016418 /*
1460116419 * The space after the text is something that google docs does.
1460616424 }
1460716425 }
1460816426
16427 function getAppendedColumnHeaderText(col) {
16428 return ', ' + i18nService.getSafeText('headerCell.aria.column') + ' ' + col.displayName;
16429 }
16430
16431 function getCellDisplayValue(currentRowColumn) {
16432 if (currentRowColumn.col.field === 'selectionRowHeaderCol') {
16433 // This is the case when the 'selection' feature is used in the grid and the user has moved
16434 // to or inside of the left grid container which holds the checkboxes for selecting rows.
16435 // This is necessary for Accessibility. Without this a screen reader cannot determine if the row
16436 // is or is not currently selected.
16437 return currentRowColumn.row.isSelected ? i18nService.getSafeText('search.aria.selected') : i18nService.getSafeText('search.aria.notSelected');
16438 } else {
16439 return grid.getCellDisplayValue(currentRowColumn.row, currentRowColumn.col);
16440 }
16441 }
16442
1460916443 var values = [];
1461016444 var currentSelection = grid.api.cellNav.getCurrentSelection();
1461116445 for (var i = 0; i < currentSelection.length; i++) {
14612 values.push(currentSelection[i].getIntersectionValueFiltered());
16446 var cellDisplayValue = getCellDisplayValue(currentSelection[i]) + getAppendedColumnHeaderText(currentSelection[i].col);
16447 values.push(cellDisplayValue);
1461316448 }
1461416449 var cellText = values.toString();
1461516450 setNotifyText(cellText);
1461616451
1461716452 });
1461816453 }
14619 addAriaLiveRegion();
16454 // Only add the ngAria stuff it will be used
16455 if (usesAria) {
16456 addAriaLiveRegion();
16457 }
1462016458 }
1462116459 };
1462216460 }
1462716465 function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
1462816466 return {
1462916467 replace: true,
14630 priority: -99999, //this needs to run very last
16468 priority: -99999, // this needs to run very last
1463116469 require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
1463216470 scope: false,
1463316471 compile: function () {
1464416482
1464516483 var grid = uiGridCtrl.grid;
1464616484
14647 //run each time a render container is created
16485 // run each time a render container is created
1464816486 uiGridCellNavService.decorateRenderContainers(grid);
1464916487
1465016488 // focusser only created for body
1465216490 return;
1465316491 }
1465416492
14655
14656
14657 if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
16493 if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells) {
1465816494 $elm.attr('aria-multiselectable', true);
14659 } else {
16495 }
16496 else {
1466016497 $elm.attr('aria-multiselectable', false);
1466116498 }
1466216499
14663 //add an element with no dimensions that can be used to set focus and capture keystrokes
16500 // add an element with no dimensions that can be used to set focus and capture keystrokes
1466416501 var focuser = $compile('<div class="ui-grid-focuser" role="region" aria-live="assertive" aria-atomic="false" tabindex="0" aria-controls="' + grid.id +'-aria-speakable '+ grid.id + '-grid-container' +'" aria-owns="' + grid.id + '-grid-container' + '"></div>')($scope);
1466516502 $elm.append(focuser);
1466616503
1467516512 }
1467616513 });
1467716514
14678 uiGridCellnavCtrl.setAriaActivedescendant = function(id){
16515 uiGridCellnavCtrl.setAriaActivedescendant = function(id) {
1467916516 $elm.attr('aria-activedescendant', id);
1468016517 };
1468116518
14682 uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
14683 if ($elm.attr('aria-activedescendant') === id){
16519 uiGridCellnavCtrl.removeAriaActivedescendant = function(id) {
16520 if ($elm.attr('aria-activedescendant') === id) {
1468416521 $elm.attr('aria-activedescendant', '');
1468516522 }
1468616523 };
1468816525
1468916526 uiGridCtrl.focus = function () {
1469016527 gridUtil.focus.byElement(focuser[0]);
14691 //allow for first time grid focus
16528 // allow for first time grid focus
1469216529 };
1469316530
1469416531 var viewPortKeyDownWasRaisedForRowCol = null;
1469616533 focuser.on('keydown', function (evt) {
1469716534 evt.uiGridTargetRenderContainerId = containerId;
1469816535 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
14699 var result = uiGridCtrl.cellNav.handleKeyDown(evt);
16536 var raiseViewPortKeyDown = uiGridCtrl.grid.options.keyDownOverrides.some(function (override) {
16537 return Object.keys(override).every( function (property) {
16538 return override[property] === evt[property];
16539 });
16540 });
16541 var result = raiseViewPortKeyDown ? null : uiGridCtrl.cellNav.handleKeyDown(evt);
1470016542 if (result === null) {
14701 uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
16543 uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol, uiGridCtrl.cellNav.handleKeyDown);
1470216544 viewPortKeyDownWasRaisedForRowCol = rowCol;
1470316545 }
1470416546 });
14705 //Bind to keypress events in the render container
14706 //keypress events are needed by edit function so the key press
14707 //that initiated an edit is not lost
14708 //must fire the event in a timeout so the editor can
14709 //initialize and subscribe to the event on another event loop
16547 // Bind to keypress events in the render container
16548 // keypress events are needed by edit function so the key press
16549 // that initiated an edit is not lost
16550 // must fire the event in a timeout so the editor can
16551 // initialize and subscribe to the event on another event loop
1471016552 focuser.on('keypress', function (evt) {
1471116553 if (viewPortKeyDownWasRaisedForRowCol) {
1471216554 $timeout(function () {
1471316555 uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
14714 },4);
16556 }, 4);
1471516557
1471616558 viewPortKeyDownWasRaisedForRowCol = null;
1471716559 }
1471816560 });
1471916561
14720 $scope.$on('$destroy', function(){
14721 //Remove all event handlers associated with this focuser.
16562 $scope.$on('$destroy', function() {
16563 // Remove all event handlers associated with this focuser.
1472216564 focuser.off();
1472316565 });
14724
1472516566 }
1472616567 };
1472716568 }
1472816569 };
1472916570 }]);
1473016571
14731 module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
14732 function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
16572 module.directive('uiGridViewport',
16573 function () {
1473316574 return {
1473416575 replace: true,
14735 priority: -99999, //this needs to run very last
16576 priority: -99999, // this needs to run very last
1473616577 require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
1473716578 scope: false,
1473816579 compile: function () {
1474716588 if (!uiGridCtrl.grid.api.cellNav) { return; }
1474816589
1474916590 var containerId = renderContainerCtrl.containerId;
14750 //no need to process for other containers
16591 // no need to process for other containers
1475116592 if (containerId !== 'body') {
1475216593 return;
1475316594 }
1475416595
1475516596 var grid = uiGridCtrl.grid;
1475616597
14757 grid.api.core.on.scrollBegin($scope, function (args) {
16598 grid.api.core.on.scrollBegin($scope, function () {
1475816599
1475916600 // Skip if there's no currently-focused cell
1476016601 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
1476216603 return;
1476316604 }
1476416605
14765 //if not in my container, move on
14766 //todo: worry about horiz scroll
16606 // if not in my container, move on
16607 // todo: worry about horiz scroll
1476716608 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
1476816609 return;
1476916610 }
1477916620 return;
1478016621 }
1478116622
14782 //if not in my container, move on
14783 //todo: worry about horiz scroll
16623 // if not in my container, move on
16624 // todo: worry about horiz scroll
1478416625 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
1478516626 return;
1478616627 }
1478716628
1478816629 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
14789
1479016630 });
1479116631
1479216632 grid.api.cellNav.on.navigate($scope, function () {
14793 //focus again because it can be lost
16633 // focus again because it can be lost
1479416634 uiGridCtrl.focus();
1479516635 });
14796
1479716636 }
1479816637 };
1479916638 }
1480016639 };
14801 }]);
16640 });
1480216641
1480316642 /**
1480416643 * @ngdoc directive
1482416663 return;
1482516664 }
1482616665
14827 //Convinience local variables
16666 // Convinience local variables
1482816667 var grid = uiGridCtrl.grid;
1482916668 $scope.focused = false;
1483016669
1484816687 */
1484916688 $elm.on('mousedown', preventMouseDown);
1485016689
14851 //turn on and off for edit events
16690 // turn on and off for edit events
1485216691 if (uiGridCtrl.grid.api.edit) {
1485316692 uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
1485416693 $elm.off('mousedown', preventMouseDown);
1486316702 });
1486416703 }
1486516704
16705 // In case we created a new row, and we are the new created row by ngRepeat
16706 // then this cell content might have been selected previously
16707 refreshCellFocus();
16708
1486616709 function preventMouseDown(evt) {
14867 //Prevents the foucus event from firing if the click event is already going to fire.
14868 //If both events fire it will cause bouncing behavior.
16710 // Prevents the foucus event from firing if the click event is already going to fire.
16711 // If both events fire it will cause bouncing behavior.
1486916712 evt.preventDefault();
1487016713 }
1487116714
14872 //You can only focus on elements with a tabindex value
16715 // You can only focus on elements with a tabindex value
1487316716 $elm.on('focus', function (evt) {
1487416717 uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
1487516718 evt.stopPropagation();
1487716720 });
1487816721
1487916722 // This event is fired for all cells. If the cell matches, then focus is set
14880 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown) {
14881 var isFocused = grid.cellNav.focusedCells.some(function(focusedRowCol, index){
16723 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, refreshCellFocus);
16724
16725 // Refresh cell focus when a new row id added to the grid
16726 var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
16727 // Clear the focus if it's set to avoid the wrong cell getting focused during
16728 // a short period of time (from now until $timeout function executed)
16729 clearFocus();
16730
16731 $scope.$applyAsync(refreshCellFocus);
16732 }, [uiGridConstants.dataChange.ROW]);
16733
16734 function refreshCellFocus() {
16735 var isFocused = grid.cellNav.focusedCells.some(function (focusedRowCol, index) {
1488216736 return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
1488316737 });
14884 if (isFocused){
16738 if (isFocused) {
1488516739 setFocused();
1488616740 } else {
1488716741 clearFocus();
1488816742 }
14889 });
16743 }
1489016744
1489116745 function setFocused() {
14892 if (!$scope.focused){
16746 if (!$scope.focused) {
1489316747 var div = $elm.find('div');
1489416748 div.addClass('ui-grid-cell-focus');
1489516749 $elm.attr('aria-selected', true);
1489916753 }
1490016754
1490116755 function clearFocus() {
14902 if ($scope.focused){
16756 if ($scope.focused) {
1490316757 var div = $elm.find('div');
1490416758 div.removeClass('ui-grid-cell-focus');
1490516759 $elm.attr('aria-selected', false);
1490916763 }
1491016764
1491116765 $scope.$on('$destroy', function () {
14912 //.off withouth paramaters removes all handlers
16766 dataChangeDereg();
16767
16768 // .off withouth paramaters removes all handlers
1491316769 $elm.find('div').off();
1491416770 $elm.off();
1491516771 });
1491616772 }
1491716773 };
1491816774 }]);
14919
1492016775 })();
1492116776
1492216777 (function () {
1495116806 */
1495216807 module.constant('uiGridEditConstants', {
1495316808 EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
14954 //must be lowercase because template bulder converts to lower
16809 // must be lowercase because template bulder converts to lower
1495516810 EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
1495616811 events: {
1495716812 BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
1499316848 * @eventOf ui.grid.edit.api:PublicApi
1499416849 * @description raised when cell editing is complete
1499516850 * <pre>
14996 * gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
16851 * gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef) {})
1499716852 * </pre>
1499816853 * @param {object} rowEntity the options.data element that was edited
1499916854 * @param {object} colDef the column that was edited
1500816863 * @eventOf ui.grid.edit.api:PublicApi
1500916864 * @description raised when cell editing starts on a cell
1501016865 * <pre>
15011 * gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
16866 * gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef) {})
1501216867 * </pre>
1501316868 * @param {object} rowEntity the options.data element that was edited
1501416869 * @param {object} colDef the column that was edited
1502316878 * @eventOf ui.grid.edit.api:PublicApi
1502416879 * @description raised when cell editing is cancelled on a cell
1502516880 * <pre>
15026 * gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
16881 * gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef) {})
1502716882 * </pre>
1502816883 * @param {object} rowEntity the options.data element that was edited
1502916884 * @param {object} colDef the column that was edited
1503816893 };
1503916894
1504016895 grid.api.registerEventsFromObject(publicApi.events);
15041 //grid.api.registerMethodsFromObject(publicApi.methods);
15042
16896 // grid.api.registerMethodsFromObject(publicApi.methods);
1504316897 },
1504416898
1504516899 defaultGridOptions: function (gridOptions) {
1506516919 * @name cellEditableCondition
1506616920 * @propertyOf ui.grid.edit.api:GridOptions
1506716921 * @description If specified, either a value or function to be used by all columns before editing.
15068 * If falsy, then editing of cell is not allowed.
16922 * If false, then editing of cell is not allowed.
1506916923 * @example
1507016924 * <pre>
15071 * function($scope){
15072 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
16925 * function($scope, triggerEvent) {
16926 * //use $scope.row.entity, $scope.col.colDef and triggerEvent to determine if editing is allowed
1507316927 * return true;
1507416928 * }
1507516929 * </pre>
1509116945 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
1509216946 * <br/>_requires cellNav feature and the edit feature to be enabled_
1509316947 */
15094 //enableCellEditOnFocus can only be used if cellnav module is used
16948 // enableCellEditOnFocus can only be used if cellnav module is used
1509516949 gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
1509616950 },
1509716951
1513016984 * @description If specified, either a value or function evaluated before editing cell. If falsy, then editing of cell is not allowed.
1513116985 * @example
1513216986 * <pre>
15133 * function($scope){
15134 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
16987 * function($scope, triggerEvent) {
16988 * //use $scope.row.entity, $scope.col.colDef and triggerEvent to determine if editing is allowed
1513516989 * return true;
1513616990 * }
1513716991 * </pre>
1516717021 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
1516817022 * <br>_requires both the cellNav feature and the edit feature to be enabled_
1516917023 */
15170 //enableCellEditOnFocus can only be used if cellnav module is used
17024 // enableCellEditOnFocus can only be used if cellnav module is used
1517117025 colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
1517217026
1517317027
1517617030 * @name editModelField
1517717031 * @propertyOf ui.grid.edit.api:ColumnDef
1517817032 * @description a bindable string value that is used when binding to edit controls instead of colDef.field
15179 * <br/> example: You have a complex property on and object like state:{abbrev:'MS',name:'Mississippi'}. The
17033 * <br/> example: You have a complex property on and object like state:{abbrev: 'MS',name: 'Mississippi'}. The
1518017034 * grid should display state.name in the cell and sort/filter based on the state.name property but the editor
1518117035 * requires the full state object.
1518217036 * <br/>colDef.field = 'state.name'
1518317037 * <br/>colDef.editModelField = 'state'
1518417038 */
15185 //colDef.editModelField
17039 // colDef.editModelField
1518617040
1518717041 return $q.all(promises);
1518817042 },
1519717051 * @returns {boolean} true if an edit should start
1519817052 */
1519917053 isStartEditKey: function (evt) {
15200 if (evt.metaKey ||
17054 return !(evt.metaKey ||
1520117055 evt.keyCode === uiGridConstants.keymap.ESC ||
1520217056 evt.keyCode === uiGridConstants.keymap.SHIFT ||
1520317057 evt.keyCode === uiGridConstants.keymap.CTRL ||
1521517069 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
1521617070
1521717071 evt.keyCode === uiGridConstants.keymap.DOWN ||
15218 evt.keyCode === uiGridConstants.keymap.ENTER) {
15219 return false;
15220
15221 }
15222 return true;
15223 }
15224
15225
17072 evt.keyCode === uiGridConstants.keymap.ENTER);
17073 }
1522617074 };
1522717075
1522817076 return service;
1529217140 function ( uiGridEditConstants) {
1529317141 return {
1529417142 replace: true,
15295 priority: -99998, //run before cellNav
17143 priority: -99998, // run before cellNav
1529617144 require: ['^uiGrid', '^uiGridRenderContainer'],
1529717145 scope: false,
1529817146 compile: function () {
1530417152 if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
1530517153
1530617154 var containerId = controllers[1].containerId;
15307 //no need to process for other containers
17155 // no need to process for other containers
1530817156 if (containerId !== 'body') {
1530917157 return;
1531017158 }
1531117159
15312 //refocus on the grid
17160 // refocus on the grid
1531317161 $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
1531417162 uiGridCtrl.focus();
1531517163 });
1531617164 $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
1531717165 uiGridCtrl.focus();
1531817166 });
15319
1532017167 }
1532117168 };
1532217169 }
1537817225 */
1537917226
1538017227 module.directive('uiGridCell',
15381 ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope',
15382 function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope) {
17228 ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope', '$q',
17229 function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope, $q) {
1538317230 var touchstartTimeout = 500;
1538417231 if ($injector.has('uiGridCellNavService')) {
1538517232 var uiGridCellNavService = $injector.get('uiGridCellNavService');
1539117238 scope: false,
1539217239 require: '?^uiGrid',
1539317240 link: function ($scope, $elm, $attrs, uiGridCtrl) {
15394 var html;
15395 var origCellValue;
15396 var inEdit = false;
15397 var cellModel;
15398 var cancelTouchstartTimeout;
17241 var html,
17242 origCellValue,
17243 cellModel,
17244 cancelTouchstartTimeout,
17245 inEdit = false;
1539917246
1540017247 var editCellScope;
1540117248
1540917256
1541017257 var setEditable = function() {
1541117258 if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
15412 if (!$scope.beginEditEventsWired) { //prevent multiple attachments
17259 if (!$scope.beginEditEventsWired) { // prevent multiple attachments
1541317260 registerBeginEditEvents();
1541417261 }
1541517262 } else {
1542817275 });
1542917276
1543017277
15431 $scope.$on( '$destroy', rowWatchDereg );
17278 $scope.$on('$destroy', function destroyEvents() {
17279 rowWatchDereg();
17280 // unbind all jquery events in order to avoid memory leaks
17281 $elm.off();
17282 });
1543217283
1543317284 function registerBeginEditEvents() {
1543417285 $elm.on('dblclick', beginEdit);
1544417295 }
1544517296
1544617297 if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
15447 //important to do this before scrollToIfNecessary
17298 // important to do this before scrollToIfNecessary
1544817299 beginEditKeyDown(evt);
1544917300 }
1545017301 });
1545117302
15452 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol) {
17303 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol, evt) {
1545317304 if ($scope.col.colDef.enableCellEditOnFocus) {
1545417305 // Don't begin edit if the cell hasn't changed
15455 if ((!oldRowCol || newRowCol.row !== oldRowCol.row || newRowCol.col !== oldRowCol.col) &&
15456 newRowCol.row === $scope.row && newRowCol.col === $scope.col) {
15457 $timeout(function () {
15458 beginEdit();
17306 if (newRowCol.row === $scope.row && newRowCol.col === $scope.col &&
17307 (evt === null || (evt && (evt.type === 'click' || evt.type === 'keydown')))) {
17308 $timeout(function() {
17309 beginEdit(evt);
1545917310 });
1546017311 }
1546117312 }
1546317314 }
1546417315
1546517316 $scope.beginEditEventsWired = true;
15466
1546717317 }
1546817318
1546917319 function touchStart(event) {
1548917339 }
1549017340
1549117341 // Cancel any touchstart timeout
15492 function touchEnd(event) {
17342 function touchEnd() {
1549317343 $timeout.cancel(cancelTouchstartTimeout);
1549417344 $elm.off('touchend', touchEnd);
1549517345 }
1550917359 }
1551017360 }
1551117361
15512 function shouldEdit(col, row) {
17362 function shouldEdit(col, row, triggerEvent) {
1551317363 return !row.isSaving &&
1551417364 ( angular.isFunction(col.colDef.cellEditableCondition) ?
15515 col.colDef.cellEditableCondition($scope) :
17365 col.colDef.cellEditableCondition($scope, triggerEvent) :
1551617366 col.colDef.cellEditableCondition );
1551717367 }
1551817368
1551917369
1552017370 function beginEdit(triggerEvent) {
15521 //we need to scroll the cell into focus before invoking the editor
17371 // we need to scroll the cell into focus before invoking the editor
1552217372 $scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
1552317373 .then(function () {
1552417374 beginEditAfterScroll(triggerEvent);
1557417424 *
1557517425 */
1557617426 /**
17427 * @ngdoc service
17428 * @name editDropdownOptionsFunction
17429 * @methodOf ui.grid.edit.api:ColumnDef
17430 * @description a function returning an array of values in the format
17431 * [ {id: xxx, value: xxx} ], which will be used to populate
17432 * the edit dropdown. This can be used when the dropdown values are dependent on
17433 * the backing row entity with some kind of algorithm.
17434 * If this property is set then both editDropdownOptionsArray and
17435 * editDropdownRowEntityOptionsArrayPath will be ignored.
17436 * @param {object} rowEntity the options.data element that the returned array refers to
17437 * @param {object} colDef the column that implements this dropdown
17438 * @returns {object} an array of values in the format
17439 * [ {id: xxx, value: xxx} ] used to populate the edit dropdown
17440 * @example
17441 * <pre>
17442 * $scope.gridOptions = {
17443 * columnDefs: [
17444 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
17445 * editDropdownOptionsFunction: function(rowEntity, colDef) {
17446 * if (rowEntity.foo === 'bar') {
17447 * return [{id: 'bar1', value: 'BAR 1'},
17448 * {id: 'bar2', value: 'BAR 2'},
17449 * {id: 'bar3', value: 'BAR 3'}];
17450 * } else {
17451 * return [{id: 'foo1', value: 'FOO 1'},
17452 * {id: 'foo2', value: 'FOO 2'}];
17453 * }
17454 * },
17455 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
17456 * ],
17457 * </pre>
17458 *
17459 */
17460 /**
1557717461 * @ngdoc property
1557817462 * @name editDropdownValueLabel
1557917463 * @propertyOf ui.grid.edit.api:ColumnDef
1561517499 return;
1561617500 }
1561717501
15618 if (!shouldEdit($scope.col, $scope.row)) {
17502 if (!shouldEdit($scope.col, $scope.row, triggerEvent)) {
1561917503 return;
1562017504 }
1562117505
15622
15623 cellModel = $parse($scope.row.getQualifiedColField($scope.col));
15624 //get original value from the cell
17506 var modelField = $scope.row.getQualifiedColField($scope.col);
17507 if ($scope.col.colDef.editModelField) {
17508 modelField = gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField);
17509 }
17510
17511 cellModel = $parse(modelField);
17512
17513 // get original value from the cell
1562517514 origCellValue = cellModel($scope);
1562617515
1562717516 html = $scope.col.editableCellTemplate;
15628
15629 if ($scope.col.colDef.editModelField) {
15630 html = html.replace(uiGridConstants.MODEL_COL_FIELD, gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField));
15631 }
15632 else {
15633 html = html.replace(uiGridConstants.MODEL_COL_FIELD, $scope.row.getQualifiedColField($scope.col));
15634 }
15635
17517 html = html.replace(uiGridConstants.MODEL_COL_FIELD, modelField);
1563617518 html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
1563717519
1563817520 var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
1563917521 html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
1564017522
1564117523 var inputType = 'text';
15642 switch ($scope.col.colDef.type){
17524 switch ($scope.col.colDef.type) {
1564317525 case 'boolean':
1564417526 inputType = 'checkbox';
1564517527 break;
1565217534 }
1565317535 html = html.replace('INPUT_TYPE', inputType);
1565417536
15655 var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
15656 if (editDropdownRowEntityOptionsArrayPath) {
15657 $scope.editDropdownOptionsArray = resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
15658 }
15659 else {
15660 $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
17537 // In order to fill dropdown options we use:
17538 // - A function/promise or
17539 // - An array inside of row entity if no function exists or
17540 // - A single array for the whole column if none of the previous exists.
17541 var editDropdownOptionsFunction = $scope.col.colDef.editDropdownOptionsFunction;
17542 if (editDropdownOptionsFunction) {
17543 $q.when(editDropdownOptionsFunction($scope.row.entity, $scope.col.colDef))
17544 .then(function(result) {
17545 $scope.editDropdownOptionsArray = result;
17546 });
17547 } else {
17548 var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
17549 if (editDropdownRowEntityOptionsArrayPath) {
17550 $scope.editDropdownOptionsArray = resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
17551 }
17552 else {
17553 $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
17554 }
1566117555 }
1566217556 $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
1566317557 $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
1566417558
15665 var cellElement;
15666 var createEditor = function(){
17559 var createEditor = function() {
1566717560 inEdit = true;
1566817561 cancelBeginEditEvents();
1566917562 var cellElement = angular.element(html);
1567917572 createEditor();
1568017573 }
1568117574
15682 //stop editing when grid is scrolled
17575 // stop editing when grid is scrolled
1568317576 var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
1568417577 if ($scope.grid.disableScrolling) {
1568517578 return;
1569117584 deregOnCancelCellEdit();
1569217585 });
1569317586
15694 //end editing
17587 // end editing
1569517588 var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
1569617589 endEdit();
1569717590 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
1570017593 deregOnCancelCellEdit();
1570117594 });
1570217595
15703 //cancel editing
17596 // cancel editing
1570417597 var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
1570517598 cancelEdit();
1570617599 deregOnCancelCellEdit();
1571017603
1571117604 $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
1571217605 $timeout(function () {
15713 //execute in a timeout to give any complex editor templates a cycle to completely render
17606 // execute in a timeout to give any complex editor templates a cycle to completely render
1571417607 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
1571517608 });
1571617609 }
1572117614 return;
1572217615 }
1572317616
15724 //sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
15725 //back to grid here. The focus call needs to be before the $destroy and removal of the control,
15726 //otherwise ng-model-options of UpdateOn: 'blur' will not work.
17617 // sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
17618 // back to grid here. The focus call needs to be before the $destroy and removal of the control,
17619 // otherwise ng-model-options of UpdateOn: 'blur' will not work.
1572717620 if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
1572817621 uiGridCtrl.focus();
1572917622 }
1573017623
1573117624 var gridCellContentsEl = angular.element($elm.children()[0]);
15732 //remove edit element
17625 // remove edit element
1573317626 editCellScope.$destroy();
15734 angular.element($elm.children()[1]).remove();
17627 var children = $elm.children();
17628 for (var i = 1; i < children.length; i++) {
17629 angular.element(children[i]).remove();
17630 }
1573517631 gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
1573617632 inEdit = false;
1573717633 registerBeginEditEvents();
1576717663 }
1576817664 return object;
1576917665 }
15770
1577117666 }
1577217667 };
1577317668 }]);
1580517700 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
1580617701 if (controllers[2]) { ngModel = controllers[2]; }
1580717702
15808 //set focus at start of edit
15809 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
17703 // set focus at start of edit
17704 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
17705 // must be in a timeout since it requires a new digest cycle
1581017706 $timeout(function () {
1581117707 $elm[0].focus();
15812 //only select text if it is not being replaced below in the cellNav viewPortKeyPress
15813 if ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav)) {
17708 // only select text if it is not being replaced below in the cellNav viewPortKeyPress
17709 if ($elm[0].select && ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav))) {
1581417710 $elm[0].select();
1581517711 }
1581617712 else {
15817 //some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
15818 //fields should not allow setSelectionRange. We ignore the error for those browsers
15819 //https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
17713 // some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
17714 // fields should not allow setSelectionRange. We ignore the error for those browsers
17715 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
1582017716 try {
1582117717 $elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
1582217718 }
1582317719 catch (ex) {
15824 //ignore
17720 // ignore
1582517721 }
1582617722 }
1582717723 });
1582817724
15829 //set the keystroke that started the edit event
15830 //we must do this because the BeginEdit is done in a different event loop than the intitial
15831 //keydown event
15832 //fire this event for the keypress that is received
17725 // set the keystroke that started the edit event
17726 // we must do this because the BeginEdit is done in a different event loop than the intitial
17727 // keydown event
17728 // fire this event for the keypress that is received
1583317729 if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
1583417730 var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
1583517731 if (uiGridEditService.isStartEditKey(evt)) {
15836 ngModel.$setViewValue(String.fromCharCode(evt.keyCode), evt);
15837 ngModel.$render();
17732 var code = typeof evt.which === 'number' ? evt.which : evt.keyCode;
17733 if (code > 0) {
17734 ngModel.$setViewValue(String.fromCharCode(code), evt);
17735 ngModel.$render();
17736 }
1583817737 }
1583917738 viewPortKeyDownUnregister();
1584017739 });
1584117740 }
1584217741
15843 $elm.on('blur', function (evt) {
15844 $scope.stopEdit(evt);
17742 // macOS will blur the checkbox when clicked in Safari and Firefox,
17743 // to get around this, we disable the blur handler on mousedown,
17744 // and then focus the checkbox and re-enable the blur handler after $timeout
17745 $elm.on('mousedown', function(evt) {
17746 if ($elm[0].type === 'checkbox') {
17747 $elm.off('blur', $scope.stopEdit);
17748 $timeout(function() {
17749 $elm[0].focus();
17750 $elm.on('blur', $scope.stopEdit);
17751 });
17752 }
1584517753 });
17754
17755 $elm.on('blur', $scope.stopEdit);
1584617756 });
1584717757
1584817758
1586317773 $elm.on('click', function (evt) {
1586417774 if ($elm[0].type !== 'checkbox') {
1586517775 $scope.deepEdit = true;
15866 $timeout(function () {
17776 $scope.$applyAsync(function () {
1586717777 $scope.grid.disableScrolling = true;
1586817778 });
1586917779 }
1589217802 }
1589317803 }
1589417804 else {
15895 //handle enter and tab for editing not using cellNav
17805 // handle enter and tab for editing not using cellNav
1589617806 switch (evt.keyCode) {
1589717807 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
1589817808 case uiGridConstants.keymap.TAB:
1590417814 }
1590517815
1590617816 return true;
17817 });
17818
17819 $scope.$on('$destroy', function unbindEvents() {
17820 // unbind all jquery events in order to avoid memory leaks
17821 $elm.off();
1590717822 });
1590817823 }
1590917824 };
1594717862 priority: -100, // run after default uiGridEditor directive
1594817863 require: '?ngModel',
1594917864 link: function (scope, element, attrs, ngModel) {
15950
1595117865 if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
15952
1595317866 ngModel.$formatters.push(function (modelValue) {
1595417867 ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
1595517868 return $filter('date')(modelValue, 'yyyy-MM-dd');
1598917902 *
1599017903 */
1599117904 module.directive('uiGridEditDropdown',
15992 ['uiGridConstants', 'uiGridEditConstants',
15993 function (uiGridConstants, uiGridEditConstants) {
17905 ['uiGridConstants', 'uiGridEditConstants', '$timeout',
17906 function (uiGridConstants, uiGridEditConstants, $timeout) {
1599417907 return {
1599517908 require: ['?^uiGrid', '?^uiGridRenderContainer'],
1599617909 scope: true,
1600317916 var uiGridCtrl = controllers[0];
1600417917 var renderContainerCtrl = controllers[1];
1600517918
16006 //set focus at start of edit
17919 // set focus at start of edit
1600717920 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
16008 $elm[0].focus();
17921 $timeout(function() {
17922 $elm[0].focus();
17923 });
17924
1600917925 $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
1601017926 $elm.on('blur', function (evt) {
1601117927 $scope.stopEdit(evt);
1603317949 }
1603417950 }
1603517951 else {
16036 //handle enter and tab for editing not using cellNav
17952 // handle enter and tab for editing not using cellNav
1603717953 switch (evt.keyCode) {
1603817954 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
1603917955 case uiGridConstants.keymap.TAB:
1604417960 }
1604517961 }
1604617962 return true;
17963 });
17964
17965 $scope.$on('$destroy', function unbindEvents() {
17966 // unbind jquery events to prevent memory leaks
17967 $elm.off();
1604717968 });
1604817969 }
1604917970 };
1606817989 *
1606917990 */
1607017991 module.directive('uiGridEditFileChooser',
16071 ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
16072 function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
17992 ['gridUtil', 'uiGridConstants', 'uiGridEditConstants',
17993 function (gridUtil, uiGridConstants, uiGridEditConstants) {
1607317994 return {
1607417995 scope: true,
1607517996 require: ['?^uiGrid', '?^uiGridRenderContainer'],
1607817999 pre: function ($scope, $elm, $attrs) {
1607918000
1608018001 },
16081 post: function ($scope, $elm, $attrs, controllers) {
16082 var uiGridCtrl, renderContainerCtrl;
16083 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16084 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16085 var grid = uiGridCtrl.grid;
16086
16087 var handleFileSelect = function( event ){
18002 post: function ($scope, $elm) {
18003 function handleFileSelect(event) {
1608818004 var target = event.srcElement || event.target;
1608918005
1609018006 if (target && target.files && target.files.length > 0) {
1610918025 *
1611018026 * @example
1611118027 * <pre>
16112 * editFileChooserCallBack: function(gridRow, gridCol, files ){
18028 * editFileChooserCallBack: function(gridRow, gridCol, files ) {
1611318029 * // ignore all but the first file, it can only choose one anyway
1611418030 * // set the filename into this column
1611518031 * gridRow.entity.filename = file[0].name;
1611618032 *
1611718033 * // read the file and set it into a hidden column, which we may do stuff with later
16118 * var setFile = function(fileContent){
18034 * var setFile = function(fileContent) {
1611918035 * gridRow.entity.file = fileContent.currentTarget.result;
1612018036 * };
1612118037 * var reader = new FileReader();
1613518051 } else {
1613618052 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
1613718053 }
16138 };
16139
16140 $elm[0].addEventListener('change', handleFileSelect, false); // TODO: why the false on the end? Google
18054 $elm[0].removeEventListener('change', handleFileSelect, false);
18055 }
18056
18057 $elm[0].addEventListener('change', handleFileSelect, false);
1614118058
1614218059 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
1614318060 $elm[0].focus();
1614418061 $elm[0].select();
1614518062
16146 $elm.on('blur', function (evt) {
18063 $elm.on('blur', function () {
1614718064 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
18065 $elm.off();
1614818066 });
1614918067 });
1615018068 }
1615218070 }
1615318071 };
1615418072 }]);
16155
18073 })();
18074
18075 (function () {
18076 'use strict';
18077
18078 /**
18079 * @ngdoc overview
18080 * @name ui.grid.emptyBaseLayer
18081 * @description
18082 *
18083 * # ui.grid.emptyBaseLayer
18084 *
18085 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
18086 *
18087 * This module provides the ability to have the background of the ui-grid be empty rows, this would be displayed in the case were
18088 * the grid height is greater then the amount of rows displayed.
18089 *
18090 * <div doc-module-components="ui.grid.emptyBaseLayer"></div>
18091 */
18092 var module = angular.module('ui.grid.emptyBaseLayer', ['ui.grid']);
18093
18094
18095 /**
18096 * @ngdoc service
18097 * @name ui.grid.emptyBaseLayer.service:uiGridBaseLayerService
18098 *
18099 * @description Services for the empty base layer grid
18100 */
18101 module.service('uiGridBaseLayerService', ['gridUtil', '$compile', function (gridUtil, $compile) {
18102 return {
18103 initializeGrid: function (grid, disableEmptyBaseLayer) {
18104
18105 /**
18106 * @ngdoc object
18107 * @name ui.grid.emptyBaseLayer.api:GridOptions
18108 *
18109 * @description GridOptions for emptyBaseLayer feature, these are available to be
18110 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
18111 */
18112 grid.baseLayer = {
18113 emptyRows: []
18114 };
18115
18116 /**
18117 * @ngdoc object
18118 * @name enableEmptyGridBaseLayer
18119 * @propertyOf ui.grid.emptyBaseLayer.api:GridOptions
18120 * @description Enable empty base layer, which shows empty rows as background on the entire grid
18121 * <br/>Defaults to true, if the directive is used.
18122 * <br/>Set to false either by setting this attribute or passing false to the directive.
18123 */
18124 // default option to true unless it was explicitly set to false
18125 if (grid.options.enableEmptyGridBaseLayer !== false) {
18126 grid.options.enableEmptyGridBaseLayer = !disableEmptyBaseLayer;
18127 }
18128 },
18129
18130 setNumberOfEmptyRows: function(viewportHeight, grid) {
18131 var rowHeight = grid.options.rowHeight,
18132 rows = Math.ceil(viewportHeight / rowHeight);
18133
18134 if (rows > 0) {
18135 grid.baseLayer.emptyRows = [];
18136 for (var i = 0; i < rows; i++) {
18137 grid.baseLayer.emptyRows.push({});
18138 }
18139 }
18140 }
18141 };
18142 }]);
18143
18144 /**
18145 * @ngdoc object
18146 * @name ui.grid.emptyBaseLayer.directive:uiGridEmptyBaseLayer
18147 * @description Shows empty rows in the background of the ui-grid, these span
18148 * the full height of the ui-grid, so that there won't be blank space below the shown rows.
18149 * @example
18150 * <pre>
18151 * <div ui-grid="gridOptions" class="grid" ui-grid-empty-base-layer></div>
18152 * </pre>
18153 * Or you can enable/disable it dynamically by passing in true or false. It doesn't
18154 * the value, so it would only be set on initial render.
18155 * <pre>
18156 * <div ui-grid="gridOptions" class="grid" ui-grid-empty-base-layer="false"></div>
18157 * </pre>
18158 */
18159 module.directive('uiGridEmptyBaseLayer', ['gridUtil', 'uiGridBaseLayerService',
18160 '$parse',
18161 function (gridUtil, uiGridBaseLayerService, $parse) {
18162 return {
18163 require: '^uiGrid',
18164 scope: false,
18165 compile: function () {
18166 return {
18167 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
18168 var disableEmptyBaseLayer = $parse($attrs.uiGridEmptyBaseLayer)($scope) === false;
18169
18170 uiGridBaseLayerService.initializeGrid(uiGridCtrl.grid, disableEmptyBaseLayer);
18171 },
18172 post: function ($scope, $elm, $attrs, uiGridCtrl) {
18173 if (!uiGridCtrl.grid.options.enableEmptyGridBaseLayer) {
18174 return;
18175 }
18176
18177 var renderBodyContainer = uiGridCtrl.grid.renderContainers.body,
18178 viewportHeight = renderBodyContainer.getViewportHeight();
18179
18180 function heightHasChanged() {
18181 var newViewPortHeight = renderBodyContainer.getViewportHeight();
18182
18183 if (newViewPortHeight !== viewportHeight) {
18184 viewportHeight = newViewPortHeight;
18185 return true;
18186 }
18187 return false;
18188 }
18189
18190 function getEmptyBaseLayerCss(viewportHeight) {
18191 // Set ui-grid-empty-base-layer height
18192 return '.grid' + uiGridCtrl.grid.id +
18193 ' .ui-grid-render-container ' +
18194 '.ui-grid-empty-base-layer-container.ui-grid-canvas ' +
18195 '{ height: ' + viewportHeight + 'px; }';
18196 }
18197
18198 uiGridCtrl.grid.registerStyleComputation({
18199 func: function() {
18200 if (heightHasChanged()) {
18201 uiGridBaseLayerService.setNumberOfEmptyRows(viewportHeight, uiGridCtrl.grid);
18202 }
18203 return getEmptyBaseLayerCss(viewportHeight);
18204 }
18205 });
18206 }
18207 };
18208 }
18209 };
18210 }]);
18211
18212 /**
18213 * @ngdoc directive
18214 * @name ui.grid.emptyBaseLayer.directive:uiGridViewport
18215 * @description stacks on the uiGridViewport directive to append the empty grid base layer html elements to the
18216 * default gridRow template
18217 */
18218 module.directive('uiGridViewport',
18219 ['$compile', 'gridUtil', '$templateCache',
18220 function ($compile, gridUtil, $templateCache) {
18221 return {
18222 priority: -200,
18223 scope: false,
18224 compile: function ($elm) {
18225 var emptyBaseLayerContainer = $templateCache.get('ui-grid/emptyBaseLayerContainer');
18226 $elm.prepend(emptyBaseLayerContainer);
18227 return {
18228 pre: function ($scope, $elm, $attrs, controllers) {
18229 },
18230 post: function ($scope, $elm, $attrs, controllers) {
18231 }
18232 };
18233 }
18234 };
18235 }]);
1615618236
1615718237 })();
1615818238
1618118261 *
1618218262 * @description Services for the expandable grid
1618318263 */
16184 module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
18264 module.service('uiGridExpandableService', ['gridUtil', function (gridUtil) {
1618518265 var service = {
1618618266 initializeGrid: function (grid) {
1618718267
1618918269 grid.expandable.expandedAll = false;
1619018270
1619118271 /**
16192 * @ngdoc object
18272 * @ngdoc boolean
18273 * @name enableOnDblClickExpand
18274 * @propertyOf ui.grid.expandable.api:GridOptions
18275 * @description Defaults to true.
18276 * @example
18277 * <pre>
18278 * $scope.gridOptions = {
18279 * onDblClickExpand: false
18280 * }
18281 * </pre>
18282 */
18283 grid.options.enableOnDblClickExpand = grid.options.enableOnDblClickExpand !== false;
18284 /**
18285 * @ngdoc boolean
1619318286 * @name enableExpandable
1619418287 * @propertyOf ui.grid.expandable.api:GridOptions
1619518288 * @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
1620218295 * </pre>
1620318296 */
1620418297 grid.options.enableExpandable = grid.options.enableExpandable !== false;
18298
18299 /**
18300 * @ngdoc object
18301 * @name showExpandAllButton
18302 * @propertyOf ui.grid.expandable.api:GridOptions
18303 * @description Whether or not to display the expand all button, allows you to hide expand all button on specific grids
18304 * within your application, or in specific modes on _this_ grid. Defaults to true.
18305 * @example
18306 * <pre>
18307 * $scope.gridOptions = {
18308 * showExpandAllButton: false
18309 * }
18310 * </pre>
18311 */
18312 grid.options.showExpandAllButton = grid.options.showExpandAllButton !== false;
1620518313
1620618314 /**
1620718315 * @ngdoc object
1622018328
1622118329 /**
1622218330 * @ngdoc object
16223 * @name
18331 * @name expandableRowHeaderWidth
1622418332 * @propertyOf ui.grid.expandable.api:GridOptions
1622518333 * @description Width in pixels of the expandable column. Defaults to 40
1622618334 * @example
1624418352 * }
1624518353 * </pre>
1624618354 */
16247 if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
18355 if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ) {
1624818356 gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
1624918357 grid.options.enableExpandable = false;
1625018358 }
1625418362 * @name ui.grid.expandable.api:PublicApi
1625518363 *
1625618364 * @description Public Api for expandable feature
18365 */
18366 /**
18367 * @ngdoc object
18368 * @name ui.grid.expandable.api:GridRow
18369 *
18370 * @description Additional properties added to GridRow when using the expandable module
1625718371 */
1625818372 /**
1625918373 * @ngdoc object
1626718381 expandable: {
1626818382 /**
1626918383 * @ngdoc event
18384 * @name rowExpandedBeforeStateChanged
18385 * @eventOf ui.grid.expandable.api:PublicApi
18386 * @description raised when row is expanding or collapsing
18387 * <pre>
18388 * gridApi.expandable.on.rowExpandedBeforeStateChanged(scope,function(row, event) {})
18389 * </pre>
18390 * @param {scope} scope the application scope
18391 * @param {GridRow} row the row that was expanded
18392 * @param {Event} evt object if raised from an event
18393 */
18394 rowExpandedBeforeStateChanged: function(scope, row, evt) {},
18395
18396 /**
18397 * @ngdoc event
1627018398 * @name rowExpandedStateChanged
1627118399 * @eventOf ui.grid.expandable.api:PublicApi
16272 * @description raised when cell editing is complete
18400 * @description raised when row expanded or collapsed
1627318401 * <pre>
16274 * gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
18402 * gridApi.expandable.on.rowExpandedStateChanged(scope,function(row, event) {})
1627518403 * </pre>
18404 * @param {scope} scope the application scope
1627618405 * @param {GridRow} row the row that was expanded
18406 * @param {Event} evt object if raised from an event
1627718407 */
16278 rowExpandedBeforeStateChanged: function(scope,row){
16279 },
16280 rowExpandedStateChanged: function (scope, row) {
16281 }
18408 rowExpandedStateChanged: function (scope, row, evt) {},
18409
18410 /**
18411 * @ngdoc event
18412 * @name rowExpandedRendered
18413 * @eventOf ui.grid.expandable.api:PublicApi
18414 * @description raised when expanded row is rendered
18415 * <pre>
18416 * gridApi.expandable.on.rowExpandedRendered(scope,function(row, event) {})
18417 * </pre>
18418 * @param {scope} scope the application scope
18419 * @param {GridRow} row the row that was expanded
18420 * @param {Event} evt object if raised from an event
18421 */
18422 rowExpandedRendered: function (scope, row, evt) {}
1628218423 }
1628318424 },
1628418425
1629018431 * @methodOf ui.grid.expandable.api:PublicApi
1629118432 * @description Toggle a specific row
1629218433 * <pre>
16293 * gridApi.expandable.toggleRowExpansion(rowEntity);
18434 * gridApi.expandable.toggleRowExpansion(rowEntity, event);
1629418435 * </pre>
1629518436 * @param {object} rowEntity the data entity for the row you want to expand
18437 * @param {Event} [e] event (if exist)
1629618438 */
16297 toggleRowExpansion: function (rowEntity) {
18439 toggleRowExpansion: function (rowEntity, e) {
1629818440 var row = grid.getRow(rowEntity);
18441
1629918442 if (row !== null) {
16300 service.toggleRowExpansion(grid, row);
18443 service.toggleRowExpansion(grid, row, e);
1630118444 }
1630218445 },
1630318446
1633818481 */
1633918482 toggleAllRows: function() {
1634018483 service.toggleAllRows(grid);
18484 },
18485 /**
18486 * @ngdoc function
18487 * @name expandRow
18488 * @methodOf ui.grid.expandable.api:PublicApi
18489 * @description Expand the data row
18490 * @param {object} rowEntity gridOptions.data[] array instance
18491 */
18492 expandRow: function (rowEntity) {
18493 var row = grid.getRow(rowEntity);
18494
18495 if (row !== null && !row.isExpanded) {
18496 service.toggleRowExpansion(grid, row);
18497 }
18498 },
18499 /**
18500 * @ngdoc function
18501 * @name collapseRow
18502 * @methodOf ui.grid.expandable.api:PublicApi
18503 * @description Collapse the data row
18504 * @param {object} rowEntity gridOptions.data[] array instance
18505 */
18506 collapseRow: function (rowEntity) {
18507 var row = grid.getRow(rowEntity);
18508
18509 if (row !== null && row.isExpanded) {
18510 service.toggleRowExpansion(grid, row);
18511 }
18512 },
18513 /**
18514 * @ngdoc function
18515 * @name getExpandedRows
18516 * @methodOf ui.grid.expandable.api:PublicApi
18517 * @description returns all expandedRow's entity references
18518 */
18519 getExpandedRows: function () {
18520 return service.getExpandedRows(grid).map(function (gridRow) {
18521 return gridRow.entity;
18522 });
1634118523 }
1634218524 }
1634318525 }
1634618528 grid.api.registerMethodsFromObject(publicApi.methods);
1634718529 },
1634818530
16349 toggleRowExpansion: function (grid, row) {
18531 /**
18532 *
18533 * @param grid
18534 * @param row
18535 * @param {Event} [e] event (if exist)
18536 */
18537 toggleRowExpansion: function (grid, row, e) {
1635018538 // trigger the "before change" event. Can change row height dynamically this way.
1635118539 grid.api.expandable.raise.rowExpandedBeforeStateChanged(row);
18540 /**
18541 * @ngdoc object
18542 * @name isExpanded
18543 * @propertyOf ui.grid.expandable.api:GridRow
18544 * @description Whether or not the row is currently expanded.
18545 * @example
18546 * <pre>
18547 * $scope.api.expandable.on.rowExpandedStateChanged($scope, function (row) {
18548 * if (row.isExpanded) {
18549 * //...
18550 * }
18551 * });
18552 * </pre>
18553 */
1635218554 row.isExpanded = !row.isExpanded;
16353 if (angular.isUndefined(row.expandedRowHeight)){
18555 if (angular.isUndefined(row.expandedRowHeight)) {
1635418556 row.expandedRowHeight = grid.options.expandableRowHeight;
1635518557 }
16356
18558
1635718559 if (row.isExpanded) {
1635818560 row.height = row.grid.options.rowHeight + row.expandedRowHeight;
18561 grid.expandable.expandedAll = service.getExpandedRows(grid).length === grid.rows.length;
1635918562 }
1636018563 else {
1636118564 row.height = row.grid.options.rowHeight;
1636218565 grid.expandable.expandedAll = false;
1636318566 }
16364 grid.api.expandable.raise.rowExpandedStateChanged(row);
18567 grid.api.expandable.raise.rowExpandedStateChanged(row, e);
18568
18569 // fire event on render complete
18570 function _tWatcher() {
18571 if (row.expandedRendered) {
18572 grid.api.expandable.raise.rowExpandedRendered(row, e);
18573 }
18574 else {
18575 window.setTimeout(_tWatcher, 1e2);
18576 }
18577 }
18578 _tWatcher();
1636518579 },
1636618580
16367 expandAllRows: function(grid, $scope) {
18581 expandAllRows: function(grid) {
1636818582 grid.renderContainers.body.visibleRowCache.forEach( function(row) {
16369 if (!row.isExpanded) {
18583 if (!row.isExpanded && !(row.entity.subGridOptions && row.entity.subGridOptions.disableRowExpandable)) {
1637018584 service.toggleRowExpansion(grid, row);
1637118585 }
1637218586 });
1639118605 else {
1639218606 service.expandAllRows(grid);
1639318607 }
18608 },
18609
18610 getExpandedRows: function (grid) {
18611 return grid.rows.filter(function (row) {
18612 return row.isExpanded;
18613 });
1639418614 }
1639518615 };
1639618616 return service;
1640918629 * }
1641018630 * </pre>
1641118631 */
18632
1641218633 module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
1641318634 function (uiGridExpandableService, $templateCache) {
1641418635 return {
1641918640 compile: function () {
1642018641 return {
1642118642 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16422 if ( uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
18643 uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
18644
18645 if (!uiGridCtrl.grid.options.enableExpandable) {
18646 return;
18647 }
18648
18649 if (uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
1642318650 var expandableRowHeaderColDef = {
1642418651 name: 'expandableButtons',
1642518652 displayName: '',
1642618653 exporterSuppressExport: true,
1642718654 enableColumnResizing: false,
1642818655 enableColumnMenu: false,
16429 width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 40
18656 width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 30
1643018657 };
18658
1643118659 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
1643218660 expandableRowHeaderColDef.headerCellTemplate = $templateCache.get('ui-grid/expandableTopRowHeader');
16433 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
18661 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef, -90);
1643418662 }
16435 uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
1643618663 },
16437 post: function ($scope, $elm, $attrs, uiGridCtrl) {
16438 }
18664 post: function ($scope, $elm, $attrs, uiGridCtrl) {}
1643918665 };
1644018666 }
1644118667 };
1644618672 * @name ui.grid.expandable.directive:uiGrid
1644718673 * @description stacks on the uiGrid directive to register child grid with parent row when child is created
1644818674 */
16449 module.directive('uiGrid', ['uiGridExpandableService', '$templateCache',
16450 function (uiGridExpandableService, $templateCache) {
18675 module.directive('uiGrid',
18676 function () {
1645118677 return {
1645218678 replace: true,
1645318679 priority: 599,
1645818684 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1645918685
1646018686 uiGridCtrl.grid.api.core.on.renderingComplete($scope, function() {
16461 //if a parent grid row is on the scope, then add the parentRow property to this childGrid
16462 if ($scope.row && $scope.row.grid && $scope.row.grid.options && $scope.row.grid.options.enableExpandable) {
18687 // if a parent grid row is on the scope, then add the parentRow property to this childGrid
18688 if ($scope.row && $scope.row.grid && $scope.row.grid.options
18689 && $scope.row.grid.options.enableExpandable) {
1646318690
1646418691 /**
1646518692 * @ngdoc directive
1647518702 */
1647618703 uiGridCtrl.grid.parentRow = $scope.row;
1647718704
16478 //todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
18705 // todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
1647918706 // uiGridCtrl.grid.core.on.canvasHeightChanged($scope, function(oldHeight, newHeight) {
1648018707 // uiGridCtrl.grid.parentRow = newHeight;
1648118708 // });
1648218709 }
16483
1648418710 });
1648518711 },
16486 post: function ($scope, $elm, $attrs, uiGridCtrl) {
16487
16488 }
18712 post: function ($scope, $elm, $attrs, uiGridCtrl) {}
1648918713 };
1649018714 }
1649118715 };
16492 }]);
18716 });
1649318717
1649418718 /**
1649518719 * @ngdoc directive
1649618720 * @name ui.grid.expandable.directive:uiGridExpandableRow
16497 * @description directive to render the expandable row template
18721 * @description directive to render the Row template on Expand
1649818722 */
1649918723 module.directive('uiGridExpandableRow',
16500 ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
16501 function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
18724 ['uiGridExpandableService', '$compile', 'uiGridConstants','gridUtil',
18725 function (uiGridExpandableService, $compile, uiGridConstants, gridUtil) {
1650218726
1650318727 return {
1650418728 replace: false,
1650518729 priority: 0,
1650618730 scope: false,
16507
1650818731 compile: function () {
1650918732 return {
16510 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
18733 pre: function ($scope, $elm) {
1651118734 gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
1651218735 function (template) {
1651318736 if ($scope.grid.options.expandableRowScope) {
18737 /**
18738 * @ngdoc object
18739 * @name expandableRowScope
18740 * @propertyOf ui.grid.expandable.api:GridOptions
18741 * @description Variables of object expandableScope will be available in the scope of the expanded subgrid
18742 * @example
18743 * <pre>
18744 * $scope.gridOptions = {
18745 * expandableRowScope: expandableScope
18746 * }
18747 * </pre>
18748 */
1651418749 var expandableRowScope = $scope.grid.options.expandableRowScope;
18750
1651518751 for (var property in expandableRowScope) {
1651618752 if (expandableRowScope.hasOwnProperty(property)) {
1651718753 $scope[property] = expandableRowScope[property];
1651818754 }
1651918755 }
1652018756 }
16521 var expandedRowElement = $compile(template)($scope);
18757 var expandedRowElement = angular.element(template);
18758
18759 expandedRowElement = $compile(expandedRowElement)($scope);
1652218760 $elm.append(expandedRowElement);
18761 $scope.row.element = $elm;
1652318762 $scope.row.expandedRendered = true;
1652418763 });
1652518764 },
1652618765
16527 post: function ($scope, $elm, $attrs, uiGridCtrl) {
18766 post: function ($scope, $elm) {
18767 $scope.row.element = $elm;
1652818768 $scope.$on('$destroy', function() {
1652918769 $scope.row.expandedRendered = false;
1653018770 });
1654018780 * @description stacks on the uiGridRow directive to add support for expandable rows
1654118781 */
1654218782 module.directive('uiGridRow',
16543 ['$compile', 'gridUtil', '$templateCache',
16544 function ($compile, gridUtil, $templateCache) {
18783 function () {
1654518784 return {
1654618785 priority: -200,
1654718786 scope: false,
16548 compile: function ($elm, $attrs) {
18787 compile: function () {
1654918788 return {
16550 pre: function ($scope, $elm, $attrs, controllers) {
18789 pre: function ($scope, $elm) {
18790 if (!$scope.grid.options.enableExpandable) {
18791 return;
18792 }
1655118793
1655218794 $scope.expandableRow = {};
1655318795
1655418796 $scope.expandableRow.shouldRenderExpand = function () {
16555 var ret = $scope.colContainer.name === 'body' && $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
16556 return ret;
18797 return $scope.colContainer.name === 'body'
18798 && $scope.grid.options.enableExpandable !== false
18799 && $scope.row.isExpanded
18800 && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
1655718801 };
1655818802
1655918803 $scope.expandableRow.shouldRenderFiller = function () {
16560 var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
16561 return ret;
18804 return $scope.row.isExpanded
18805 && (
18806 $scope.colContainer.name !== 'body'
18807 || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
1656218808 };
1656318809
16564 /*
16565 * Commented out @PaulL1. This has no purpose that I can see, and causes #2964. If this code needs to be reinstated for some
16566 * reason it needs to use drawnWidth, not width, and needs to check column visibility. It should really use render container
16567 * visible column cache also instead of checking column.renderContainer.
16568 function updateRowContainerWidth() {
16569 var grid = $scope.grid;
16570 var colWidth = 0;
16571 grid.columns.forEach( function (column) {
16572 if (column.renderContainer === 'left') {
16573 colWidth += column.width;
16574 }
16575 });
16576 colWidth = Math.floor(colWidth);
16577 return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
16578 ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
16579 ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
16580 }
16581
16582 if ($scope.colContainer.name === 'left') {
16583 $scope.grid.registerStyleComputation({
16584 priority: 15,
16585 func: updateRowContainerWidth
16586 });
16587 }*/
16588
18810 if ($scope.grid.options.enableOnDblClickExpand) {
18811 $elm.on('dblclick', function (event) {
18812 // if necessary, it is possible for everyone to stop the processing of a single click OR
18813 // Inside the Config in the output agent to enter a line:
18814 // event.stopPropagation()
18815 $scope.grid.api.expandable.toggleRowExpansion($scope.row.entity, event);
18816 });
18817 }
1658918818 },
16590 post: function ($scope, $elm, $attrs, controllers) {
16591 }
18819 post: function ($scope, $elm, $attrs, controllers) {}
1659218820 };
1659318821 }
1659418822 };
16595 }]);
18823 });
1659618824
1659718825 /**
1659818826 * @ngdoc directive
1660618834 return {
1660718835 priority: -200,
1660818836 scope: false,
16609 compile: function ($elm, $attrs) {
16610 var rowRepeatDiv = angular.element($elm.children().children()[0]);
16611 var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
16612 var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
18837 compile: function ($elm) {
18838
18839 // todo: this adds ng-if watchers to each row even if the grid is not using expandable directive
18840 // or options.enableExpandable == false
18841 // The alternative is to compile the template and append to each row in a uiGridRow directive
18842
18843 var rowRepeatDiv = angular.element($elm.children().children()[0]),
18844 expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller'),
18845 expandedRowElement = $templateCache.get('ui-grid/expandableRow');
18846
1661318847 rowRepeatDiv.append(expandedRowElement);
1661418848 rowRepeatDiv.append(expandedRowFillerElement);
1661518849 return {
1662418858
1662518859 })();
1662618860
18861 /* global ExcelBuilder */
1662718862 /* global console */
1662818863
1662918864 (function () {
1663818873 *
1663918874 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
1664018875 *
16641 * This module provides the ability to exporter data from the grid.
18876 * This module provides the ability to export data from the grid.
1664218877 *
1664318878 * Data can be exported in a range of formats, and all data, visible
1664418879 * data, or selected rows can be exported, with all columns or visible
1668518920 */
1668618921 module.constant('uiGridExporterConstants', {
1668718922 featureName: 'exporter',
18923 rowHeaderColName: 'treeBaseRowHeaderCol',
18924 selectionRowHeaderColName: 'selectionRowHeaderCol',
1668818925 ALL: 'all',
1668918926 VISIBLE: 'visible',
1669018927 SELECTED: 'selected',
1669918936 *
1670018937 * @description Services for exporter feature
1670118938 */
16702 module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
16703 function ($q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
16704
18939 module.service('uiGridExporterService', ['$filter', '$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
18940 function ($filter, $q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
1670518941 var service = {
1670618942
1670718943 delay: 100,
1670818944
1670918945 initializeGrid: function (grid) {
1671018946
16711 //add feature namespace and any properties to grid for needed state
18947 // add feature namespace and any properties to grid for needed state
1671218948 grid.exporter = {};
1671318949 this.defaultGridOptions(grid.options);
1671418950
1675718993 */
1675818994 pdfExport: function (rowTypes, colTypes) {
1675918995 service.pdfExport(grid, rowTypes, colTypes);
18996 },
18997 /**
18998 * @ngdoc function
18999 * @name excelExport
19000 * @methodOf ui.grid.exporter.api:PublicApi
19001 * @description Exports rows from the grid in excel format,
19002 * the data exported is selected based on the provided options
19003 * @param {string} rowTypes which rows to export, valid values are
19004 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
19005 * uiGridExporterConstants.SELECTED
19006 * @param {string} colTypes which columns to export, valid values are
19007 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
19008 */
19009 excelExport: function (rowTypes, colTypes) {
19010 service.excelExport(grid, rowTypes, colTypes);
1676019011 }
1676119012 }
1676219013 }
1676619017
1676719018 grid.api.registerMethodsFromObject(publicApi.methods);
1676819019
16769 if (grid.api.core.addToGridMenu){
19020 if (grid.api.core.addToGridMenu) {
1677019021 service.addToMenu( grid );
1677119022 } else {
1677219023 // order of registration is not guaranteed, register in a little while
1677319024 $interval( function() {
16774 if (grid.api.core.addToGridMenu){
19025 if (grid.api.core.addToGridMenu) {
1677519026 service.addToMenu( grid );
1677619027 }
1677719028 }, this.delay, 1);
1678019031 },
1678119032
1678219033 defaultGridOptions: function (gridOptions) {
16783 //default option to true unless it was explicitly set to false
19034 // default option to true unless it was explicitly set to false
1678419035 /**
1678519036 * @ngdoc object
1678619037 * @name ui.grid.exporter.api:GridOptions
1685219103 gridOptions.exporterPdfFilename = gridOptions.exporterPdfFilename ? gridOptions.exporterPdfFilename : 'download.pdf';
1685319104 /**
1685419105 * @ngdoc object
19106 * @name exporterExcelFilename
19107 * @propertyOf ui.grid.exporter.api:GridOptions
19108 * @description The default filename to use when saving the downloaded excel, only used in IE (other browsers open excels in a new window)
19109 * <br/>Defaults to 'download.xlsx'
19110 */
19111 gridOptions.exporterExcelFilename = gridOptions.exporterExcelFilename ? gridOptions.exporterExcelFilename : 'download.xlsx';
19112
19113 /**
19114 * @ngdoc object
19115 * @name exporterExcelSheetName
19116 * @propertyOf ui.grid.exporter.api:GridOptions
19117 * @description The default sheetname to use when saving the downloaded to excel
19118 * <br/>Defaults to 'Sheet1'
19119 */
19120 gridOptions.exporterExcelSheetName = gridOptions.exporterExcelSheetName ? gridOptions.exporterExcelSheetName : 'Sheet1';
19121
19122 /**
19123 * @ngdoc object
1685519124 * @name exporterOlderExcelCompatibility
1685619125 * @propertyOf ui.grid.exporter.api:GridOptions
1685719126 * @description Some versions of excel don't like the utf-16 BOM on the front, and it comes
1686019129 * <br/>Defaults to false
1686119130 */
1686219131 gridOptions.exporterOlderExcelCompatibility = gridOptions.exporterOlderExcelCompatibility === true;
19132 /**
19133 * @ngdoc object
19134 * @name exporterIsExcelCompatible
19135 * @propertyOf ui.grid.exporter.api:GridOptions
19136 * @description Separator header, used to set a custom column separator in a csv file, only works on MS Excel.
19137 * Used it on other programs will make csv content display unproperly. Setting this option to false won't add this header.
19138 * <br/>Defaults to false
19139 */
19140 gridOptions.exporterIsExcelCompatible = gridOptions.exporterIsExcelCompatible === true;
19141 /**
19142 * @ngdoc object
19143 * @name exporterMenuItemOrder
19144 * @propertyOf ui.grid.exporter.api:GridOptions
19145 * @description An option to determine the starting point for the menu items created by the exporter
19146 * <br/>Defaults to 200
19147 */
19148 gridOptions.exporterMenuItemOrder = gridOptions.exporterMenuItemOrder ? gridOptions.exporterMenuItemOrder : 200;
1686319149 /**
1686419150 * @ngdoc object
1686519151 * @name exporterPdfDefaultStyle
1699119277 /**
1699219278 * @ngdoc object
1699319279 * @name exporterMenuAllData
16994 * @porpertyOf ui.grid.exporter.api:GridOptions
19280 * @propertyOf ui.grid.exporter.api:GridOptions
1699519281 * @description Add export all data as cvs/pdf menu items to the ui-grid grid menu, if it's present. Defaults to true.
1699619282 */
1699719283 gridOptions.exporterMenuAllData = gridOptions.exporterMenuAllData !== undefined ? gridOptions.exporterMenuAllData : true;
1699819284
1699919285 /**
1700019286 * @ngdoc object
19287 * @name exporterMenuVisibleData
19288 * @propertyOf ui.grid.exporter.api:GridOptions
19289 * @description Add export visible data as cvs/pdf menu items to the ui-grid grid menu, if it's present. Defaults to true.
19290 */
19291 gridOptions.exporterMenuVisibleData = gridOptions.exporterMenuVisibleData !== undefined ? gridOptions.exporterMenuVisibleData : true;
19292
19293 /**
19294 * @ngdoc object
19295 * @name exporterMenuSelectedData
19296 * @propertyOf ui.grid.exporter.api:GridOptions
19297 * @description Add export selected data as cvs/pdf menu items to the ui-grid grid menu, if it's present. Defaults to true.
19298 */
19299 gridOptions.exporterMenuSelectedData = gridOptions.exporterMenuSelectedData !== undefined ? gridOptions.exporterMenuSelectedData : true;
19300
19301 /**
19302 * @ngdoc object
1700119303 * @name exporterMenuCsv
17002 * @propertyOf ui.grid.exporter.api:GridOptions
19304 * @propertyOf ui.grid.exporter.api:GridOptions
1700319305 * @description Add csv export menu items to the ui-grid grid menu, if it's present. Defaults to true.
1700419306 */
1700519307 gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
1701119313 * @description Add pdf export menu items to the ui-grid grid menu, if it's present. Defaults to true.
1701219314 */
1701319315 gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
19316
19317 /**
19318 * @ngdoc object
19319 * @name exporterMenuExcel
19320 * @propertyOf ui.grid.exporter.api:GridOptions
19321 * @description Add excel export menu items to the ui-grid grid menu, if it's present. Defaults to true.
19322 */
19323 gridOptions.exporterMenuExcel = gridOptions.exporterMenuExcel !== undefined ? gridOptions.exporterMenuExcel : true;
1701419324
1701519325 /**
1701619326 * @ngdoc object
1706019370 *
1706119371 * @example
1706219372 * <pre>
17063 * gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; };
19373 * gridOptions.exporterHeaderFilter = function( displayName ) { return 'col: ' + name; };
1706419374 * </pre>
1706519375 * OR
1706619376 * <pre>
1708219392 *
1708319393 * @param {Grid} grid provides the grid in case you have need of it
1708419394 * @param {GridRow} row the row from which the data comes
17085 * @param {GridCol} col the column from which the data comes
19395 * @param {GridColumn} col the column from which the data comes
1708619396 * @param {object} value the value for your massaging
1708719397 * @returns {object} you must return the massaged value ready for exporting
1708819398 *
1708919399 * @example
1709019400 * <pre>
17091 * gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
17092 * if ( col.name === 'status' ){
19401 * gridOptions.exporterFieldCallback = function ( grid, row, col, value ) {
19402 * if ( col.name === 'status' ) {
1709319403 * value = decodeStatus( value );
1709419404 * }
1709519405 * return value;
1709619406 * }
1709719407 * </pre>
1709819408 */
17099 gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
19409 gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : defaultExporterFieldCallback;
19410
19411 /**
19412 * @ngdoc function
19413 * @name exporterFieldFormatCallback
19414 * @propertyOf ui.grid.exporter.api:GridOptions
19415 * @description A function to call for each field before exporting it. Allows
19416 * general object to be return to modify the format of a cell in the case of
19417 * excel exports
19418 *
19419 * The method is called once for each field exported, and provides the grid, the
19420 * gridCol and the GridRow for you to use as context in massaging the data.
19421 *
19422 * @param {Grid} grid provides the grid in case you have need of it
19423 * @param {GridRow} row the row from which the data comes
19424 * @param {GridColumn} col the column from which the data comes
19425 * @param {object} value the value for your massaging
19426 * @returns {object} you must return the massaged value ready for exporting
19427 *
19428 * @example
19429 * <pre>
19430 * gridOptions.exporterFieldCallback = function ( grid, row, col, value ) {
19431 * if ( col.name === 'status' ) {
19432 * value = decodeStatus( value );
19433 * }
19434 * return value;
19435 * }
19436 * </pre>
19437 */
19438 gridOptions.exporterFieldFormatCallback = gridOptions.exporterFieldFormatCallback ? gridOptions.exporterFieldFormatCallback : function( grid, row, col, value ) { return null; };
19439
19440 /**
19441 * @ngdoc function
19442 * @name exporterExcelCustomFormatters
19443 * @propertyOf ui.grid.exporter.api:GridOptions
19444 * @description A function to call to setup formatters and store on docDefinition.
19445 *
19446 * The method is called at the start and can setup all the formatters to export to excel
19447 *
19448 * @param {Grid} grid provides the grid in case you have need of it
19449 * @param {Workbook} row the row from which the data comes
19450 * @param {docDefinition} The docDefinition that will have styles as a object to store formatters
19451 * @returns {docDefinition} Updated docDefinition with formatter styles
19452 *
19453 * @example
19454 * <pre>
19455 * gridOptions.exporterExcelCustomFormatters = function(grid, workbook, docDefinition) {
19456 * const formatters = {};
19457 * const stylesheet = workbook.getStyleSheet();
19458 * const headerFormatDefn = {
19459 * 'font': { 'size': 11, 'fontName': 'Calibri', 'bold': true },
19460 * 'alignment': { 'wrapText': false }
19461 * };
19462 *
19463 * formatters['header'] = headerFormatter;
19464 * Object.assign(docDefinition.styles , formatters);
19465 * grid.docDefinition = docDefinition;
19466 * return docDefinition;
19467 * }
19468 * </pre>
19469 */
19470 gridOptions.exporterExcelCustomFormatters = gridOptions.exporterExcelCustomFormatters ? gridOptions.exporterExcelCustomFormatters : function( grid, workbook, docDefinition ) { return docDefinition; };
19471
19472 /**
19473 * @ngdoc function
19474 * @name exporterExcelHeader
19475 * @propertyOf ui.grid.exporter.api:GridOptions
19476 * @description A function to write formatted header data to sheet.
19477 *
19478 * The method is called to provide custom header building for Excel. This data comes before the grid header
19479 *
19480 * @param {grid} grid provides the grid in case you have need of it
19481 * @param {Workbook} row the row from which the data comes
19482 * @param {Sheet} the sheet to insert data
19483 * @param {docDefinition} The docDefinition that will have styles as a object to store formatters
19484 * @returns {docDefinition} Updated docDefinition with formatter styles
19485 *
19486 * @example
19487 * <pre>
19488 * gridOptions.exporterExcelCustomFormatters = function (grid, workbook, sheet, docDefinition) {
19489 * const headerFormatter = docDefinition.styles['header'];
19490 * let cols = [];
19491 * // push data in A1 cell with metadata formatter
19492 * cols.push({ value: 'Summary Report', metadata: {style: headerFormatter.id} });
19493 * sheet.data.push(cols);
19494 * }
19495 * </pre>
19496 */
19497 gridOptions.exporterExcelHeader = gridOptions.exporterExcelHeader ? gridOptions.exporterExcelHeader : function( grid, workbook, sheet, docDefinition ) { return null; };
19498
19499
19500 /**
19501 * @ngdoc object
19502 * @name exporterColumnScaleFactor
19503 * @propertyOf ui.grid.exporter.api:GridOptions
19504 * @description A scaling factor to divide the drawnwidth of a column to convert to target excel column
19505 * format size
19506 * @example
19507 * In this example we add a number to divide the drawnwidth of a column to get the excel width.
19508 * <br/>Defaults to 3.5
19509 */
19510 gridOptions.exporterColumnScaleFactor = gridOptions.exporterColumnScaleFactor ? gridOptions.exporterColumnScaleFactor : 3.5;
19511
19512 /**
19513 * @ngdoc object
19514 * @name exporterFieldApplyFilters
19515 * @propertyOf ui.grid.exporter.api:GridOptions
19516 * @description Defaults to false, which leads to filters being evaluated on export *
19517 *
19518 * @example
19519 * <pre>
19520 * gridOptions.exporterFieldApplyFilters = true;
19521 * </pre>
19522 */
19523 gridOptions.exporterFieldApplyFilters = gridOptions.exporterFieldApplyFilters === true;
1710019524
1710119525 /**
1710219526 * @ngdoc function
1713219556 * }
1713319557 * </pre>
1713419558 */
17135 if ( gridOptions.exporterAllDataFn == null && gridOptions.exporterAllDataPromise ) {
19559 if ( gridOptions.exporterAllDataFn === null && gridOptions.exporterAllDataPromise ) {
1713619560 gridOptions.exporterAllDataFn = gridOptions.exporterAllDataPromise;
1713719561 }
1713819562 },
1715019574 grid.api.core.addToGridMenu( grid, [
1715119575 {
1715219576 title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
17153 action: function ($event) {
17154 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
19577 action: function () {
19578 grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
1715519579 },
1715619580 shown: function() {
17157 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuAllData;
19581 return grid.options.exporterMenuCsv && grid.options.exporterMenuAllData;
1715819582 },
17159 order: 200
19583 order: grid.options.exporterMenuItemOrder
1716019584 },
1716119585 {
1716219586 title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
17163 action: function ($event) {
17164 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
19587 action: function () {
19588 grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
1716519589 },
1716619590 shown: function() {
17167 return this.grid.options.exporterMenuCsv;
19591 return grid.options.exporterMenuCsv && grid.options.exporterMenuVisibleData;
1716819592 },
17169 order: 201
19593 order: grid.options.exporterMenuItemOrder + 1
1717019594 },
1717119595 {
1717219596 title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
17173 action: function ($event) {
17174 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
19597 action: function () {
19598 grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
1717519599 },
1717619600 shown: function() {
17177 return this.grid.options.exporterMenuCsv &&
17178 ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
19601 return grid.options.exporterMenuCsv && grid.options.exporterMenuSelectedData &&
19602 ( grid.api.selection && grid.api.selection.getSelectedRows().length > 0 );
1717919603 },
17180 order: 202
19604 order: grid.options.exporterMenuItemOrder + 2
1718119605 },
1718219606 {
1718319607 title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
17184 action: function ($event) {
17185 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
19608 action: function () {
19609 grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
1718619610 },
1718719611 shown: function() {
17188 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuAllData;
19612 return grid.options.exporterMenuPdf && grid.options.exporterMenuAllData;
1718919613 },
17190 order: 203
19614 order: grid.options.exporterMenuItemOrder + 3
1719119615 },
1719219616 {
1719319617 title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
17194 action: function ($event) {
17195 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
19618 action: function () {
19619 grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
1719619620 },
1719719621 shown: function() {
17198 return this.grid.options.exporterMenuPdf;
19622 return grid.options.exporterMenuPdf && grid.options.exporterMenuVisibleData;
1719919623 },
17200 order: 204
19624 order: grid.options.exporterMenuItemOrder + 4
1720119625 },
1720219626 {
1720319627 title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
17204 action: function ($event) {
17205 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
19628 action: function () {
19629 grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
1720619630 },
1720719631 shown: function() {
17208 return this.grid.options.exporterMenuPdf &&
17209 ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
19632 return grid.options.exporterMenuPdf && grid.options.exporterMenuSelectedData &&
19633 ( grid.api.selection && grid.api.selection.getSelectedRows().length > 0 );
1721019634 },
17211 order: 205
19635 order: grid.options.exporterMenuItemOrder + 5
19636 },
19637 {
19638 title: i18nService.getSafeText('gridMenu.exporterAllAsExcel'),
19639 action: function () {
19640 grid.api.exporter.excelExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
19641 },
19642 shown: function() {
19643 return grid.options.exporterMenuExcel && grid.options.exporterMenuAllData;
19644 },
19645 order: grid.options.exporterMenuItemOrder + 6
19646 },
19647 {
19648 title: i18nService.getSafeText('gridMenu.exporterVisibleAsExcel'),
19649 action: function () {
19650 grid.api.exporter.excelExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
19651 },
19652 shown: function() {
19653 return grid.options.exporterMenuExcel && grid.options.exporterMenuVisibleData;
19654 },
19655 order: grid.options.exporterMenuItemOrder + 7
19656 },
19657 {
19658 title: i18nService.getSafeText('gridMenu.exporterSelectedAsExcel'),
19659 action: function () {
19660 grid.api.exporter.excelExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
19661 },
19662 shown: function() {
19663 return grid.options.exporterMenuExcel && grid.options.exporterMenuSelectedData &&
19664 ( grid.api.selection && grid.api.selection.getSelectedRows().length > 0 );
19665 },
19666 order: grid.options.exporterMenuItemOrder + 8
1721219667 }
1721319668 ]);
1721419669 },
1723519690 var exportData = self.getData(grid, rowTypes, colTypes);
1723619691 var csvContent = self.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
1723719692
17238 self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterOlderExcelCompatibility);
19693 self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterCsvColumnSeparator, grid.options.exporterOlderExcelCompatibility, grid.options.exporterIsExcelCompatible);
1723919694 });
1724019695 },
1724119696
1725819713 loadAllDataIfNeeded: function (grid, rowTypes, colTypes) {
1725919714 if ( rowTypes === uiGridExporterConstants.ALL && grid.rows.length !== grid.options.totalItems && grid.options.exporterAllDataFn) {
1726019715 return grid.options.exporterAllDataFn()
17261 .then(function() {
17262 grid.modifyRows(grid.options.data);
19716 .then(function(allData) {
19717 grid.modifyRows(allData);
1726319718 });
1726419719 } else {
1726519720 var deferred = $q.defer();
1729319748 * uiGridExporterConstants.SELECTED
1729419749 */
1729519750 getColumnHeaders: function (grid, colTypes) {
17296 var headers = [];
17297 var columns;
17298
17299 if ( colTypes === uiGridExporterConstants.ALL ){
19751 var headers = [],
19752 columns;
19753
19754 if ( colTypes === uiGridExporterConstants.ALL ) {
1730019755 columns = grid.columns;
1730119756 } else {
17302 var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17303 var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17304 var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17305
17306 columns = leftColumns.concat(bodyColumns,rightColumns);
17307 }
17308
17309 columns.forEach( function( gridCol, index ) {
17310 if ( gridCol.colDef.exporterSuppressExport !== true &&
17311 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17312 headers.push({
19757 var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ) { return column.visible; } ) : [],
19758 bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ) { return column.visible; } ) : [],
19759 rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ) { return column.visible; } ) : [];
19760
19761 columns = leftColumns.concat(bodyColumns, rightColumns);
19762 }
19763
19764 columns.forEach( function( gridCol ) {
19765 // $$hashKey check since when grouping and sorting pragmatically this ends up in export. Filtering it out
19766 if ( gridCol.colDef.exporterSuppressExport !== true && gridCol.field !== '$$hashKey' &&
19767 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ) {
19768 var headerEntry = {
1731319769 name: gridCol.field,
17314 displayName: grid.options.exporterHeaderFilter ? ( grid.options.exporterHeaderFilterUseName ? grid.options.exporterHeaderFilter(gridCol.name) : grid.options.exporterHeaderFilter(gridCol.displayName) ) : gridCol.displayName,
19770 displayName: getDisplayName(grid, gridCol),
1731519771 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
17316 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
17317 });
19772 align: gridCol.colDef.align ? gridCol.colDef.align : (gridCol.colDef.type === 'number' ? 'right' : 'left')
19773 };
19774
19775 headers.push(headerEntry);
1731819776 }
1731919777 });
1732019778
1732119779 return headers;
1732219780 },
17323
1732419781
1732519782 /**
1732619783 * @ngdoc property
1733119788 * valid pdfMake alignment option.
1733219789 */
1733319790
17334
1733519791 /**
1733619792 * @ngdoc object
1733719793 * @name ui.grid.exporter.api:GridRow
1733819794 * @description GridRow settings for exporter
1733919795 */
19796
1734019797 /**
1734119798 * @ngdoc object
1734219799 * @name exporterEnableExporting
1734519802 * other settings
1734619803 * <br/>Defaults to true
1734719804 */
19805
19806 /**
19807 * @ngdoc function
19808 * @name getRowsFromNode
19809 * @methodOf ui.grid.exporter.service:uiGridExporterService
19810 * @description Gets rows from a node. If the node is grouped it will
19811 * recurse down into the children to get to the raw data element
19812 * which is a row without children (a leaf).
19813 * @param {Node} aNode the tree node on the grid
19814 * @returns {Array} an array of leaf nodes
19815 */
19816 getRowsFromNode: function(aNode) {
19817 var rows = [];
19818 for (var i = 0; i<aNode.children.length; i++) {
19819 if (aNode.children[i].children && aNode.children[i].children.length === 0) {
19820 rows.push(aNode.children[i]);
19821 } else {
19822 var nodeRows = this.getRowsFromNode(aNode.children[i]);
19823 rows = rows.concat(nodeRows);
19824 }
19825 }
19826 return rows;
19827 },
19828
19829 /**
19830 * @ngdoc function
19831 * @name getDataSorted
19832 * @methodOf ui.grid.exporter.service:uiGridExporterService
19833 * @description Gets rows from a node. If the node is grouped it will
19834 * recurse down into the children to get to the raw data element
19835 * which is a row without children (a leaf). If the grid is not
19836 * grouped this will return just the raw rows
19837 * @param {Grid} grid the grid from which data should be exported
19838 * @returns {Array} an array of leaf nodes
19839 */
19840 getDataSorted: function (grid) {
19841 if (!grid.treeBase || grid.treeBase.numberLevels === 0) {
19842 return grid.rows;
19843 }
19844 var rows = [];
19845
19846 for (var i = 0; i< grid.treeBase.tree.length; i++) {
19847 var nodeRows = this.getRowsFromNode(grid.treeBase.tree[i]);
19848
19849 for (var j = 0; j<nodeRows.length; j++) {
19850 rows.push(nodeRows[j].row);
19851 }
19852 }
19853 return rows;
19854 },
1734819855
1734919856 /**
1735019857 * @ngdoc function
1736019867 * @param {string} colTypes which columns to export, valid values are
1736119868 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
1736219869 * uiGridExporterConstants.SELECTED
19870 * @param {boolean} applyCellFilters whether or not to get the display value or the raw value of the data
1736319871 */
17364 getData: function (grid, rowTypes, colTypes) {
17365 var data = [];
17366 var rows;
17367 var columns;
19872 getData: function (grid, rowTypes, colTypes, applyCellFilters) {
19873 var data = [],
19874 rows,
19875 columns;
1736819876
1736919877 switch ( rowTypes ) {
1737019878 case uiGridExporterConstants.ALL:
17371 rows = grid.rows;
19879 rows = this.getDataSorted(grid, rowTypes, colTypes, applyCellFilters);
1737219880 break;
1737319881 case uiGridExporterConstants.VISIBLE:
1737419882 rows = grid.getVisibleRows();
1737519883 break;
1737619884 case uiGridExporterConstants.SELECTED:
17377 if ( grid.api.selection ){
19885 if ( grid.api.selection ) {
1737819886 rows = grid.api.selection.getSelectedGridRows();
1737919887 } else {
1738019888 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
1738219890 break;
1738319891 }
1738419892
17385 if ( colTypes === uiGridExporterConstants.ALL ){
19893 if ( colTypes === uiGridExporterConstants.ALL ) {
1738619894 columns = grid.columns;
1738719895 } else {
17388 var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17389 var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17390 var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
17391
17392 columns = leftColumns.concat(bodyColumns,rightColumns);
17393 }
17394
17395 rows.forEach( function( row, index ) {
17396
19896 var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ) { return column.visible; } ) : [];
19897 var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ) { return column.visible; } ) : [];
19898 var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ) { return column.visible; } ) : [];
19899
19900 columns = leftColumns.concat(bodyColumns, rightColumns);
19901 }
19902
19903 rows.forEach(function( row ) {
1739719904 if (row.exporterEnableExporting !== false) {
1739819905 var extractedRow = [];
1739919906
17400
17401 columns.forEach( function( gridCol, index ) {
17402 if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
17403 gridCol.colDef.exporterSuppressExport !== true &&
17404 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
17405 var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, grid.getCellValue( row, gridCol ) ) };
19907 columns.forEach( function( gridCol ) {
19908 // $$hashKey check since when grouping and sorting programmatically this ends up in export. Filtering it out
19909 if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
19910 gridCol.colDef.exporterSuppressExport !== true && gridCol.field !== '$$hashKey' &&
19911 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ) {
19912 var cellValue = applyCellFilters ? grid.getCellDisplayValue( row, gridCol ) : grid.getCellValue( row, gridCol ),
19913 extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, cellValue ) },
19914 extension = grid.options.exporterFieldFormatCallback( grid, row, gridCol, cellValue );
19915
19916 if (extension) {
19917 Object.assign(extractedField, extension);
19918 }
1740619919 if ( gridCol.colDef.exporterPdfAlign ) {
1740719920 extractedField.alignment = gridCol.colDef.exporterPdfAlign;
1740819921 }
1742019933
1742119934 /**
1742219935 * @ngdoc function
17423 * @name formatAsCSV
19936 * @name formatAsCsv
1742419937 * @methodOf ui.grid.exporter.service:uiGridExporterService
1742519938 * @description Formats the column headers and data as a CSV,
1742619939 * and sends that data to the user
1742819941 * where each header is an object with name, width and maybe alignment
1742919942 * @param {array} exportData an array of rows, where each row is
1743019943 * an array of column data
19944 * @param {string} separator a string that represents the separator to be used in the csv file
1743119945 * @returns {string} csv the formatted csv as a string
1743219946 */
1743319947 formatAsCsv: function (exportColumnHeaders, exportData, separator) {
17434 var self = this;
17435
17436 var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
17437
17438 var csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
19948 var self = this,
19949 bareHeaders = exportColumnHeaders.map(function(header) { return { value: header.displayName };}),
19950 csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
1743919951
1744019952 csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
1744119953
1744919961 * @description Renders a single field as a csv field, including
1745019962 * quotes around the value
1745119963 * @param {exporterService} exporter pass in exporter
17452 * @param {array} row the row to be turned into a csv string
17453 * @returns {string} a csv-ified version of the row
19964 * @param {string} separator the string to be used to join the row data
19965 * @returns {function} A function that returns a csv-ified version of the row
1745419966 */
1745519967 formatRowAsCsv: function (exporter, separator) {
1745619968 return function (row) {
1748519997 return JSON.stringify(field.value);
1748619998 },
1748719999
17488
1748920000 /**
1749020001 * @ngdoc function
1749120002 * @name isIE
17492 * @methodOf ui.grid.exporter.service:uiGridExporterService
20003 * @methodOf ui.grid.exporter.service:uiGridExporterService
1749320004 * @description Checks whether current browser is IE and returns it's version if it is
1749420005 */
1749520006 isIE: function () {
17496 var match = navigator.userAgent.match(/(?:MSIE |Trident\/.*; rv:)(\d+)/);
17497 return match ? parseInt(match[1]) : false;
20007 var match = navigator.userAgent.search(/(?:Edge|MSIE|Trident\/.*; rv:)/);
20008 var isIE = false;
20009
20010 if (match !== -1) {
20011 isIE = true;
20012 }
20013
20014 return isIE;
1749820015 },
1749920016
1750020017
1750120018 /**
1750220019 * @ngdoc function
1750320020 * @name downloadFile
17504 * @methodOf ui.grid.exporter.service:uiGridExporterService
20021 * @methodOf ui.grid.exporter.service:uiGridExporterService
1750520022 * @description Triggers download of a csv file. Logic provided
1750620023 * by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
1750720024 * @param {string} fileName the filename we'd like our file to be
1750820025 * given
1750920026 * @param {string} csvContent the csv content that we'd like to
1751020027 * download as a file
20028 * @param {string} columnSeparator The separator to be used by the columns
1751120029 * @param {boolean} exporterOlderExcelCompatibility whether or not we put a utf-16 BOM on the from (\uFEFF)
20030 * @param {boolean} exporterIsExcelCompatible whether or not we add separator header ('sep=X')
1751220031 */
17513 downloadFile: function (fileName, csvContent, exporterOlderExcelCompatibility) {
17514 var D = document;
17515 var a = D.createElement('a');
17516 var strMimeType = 'application/octet-stream;charset=utf-8';
17517 var rawFile;
17518 var ieVersion;
17519
17520 ieVersion = this.isIE();
17521 if (ieVersion && ieVersion < 10) {
17522 var frame = D.createElement('iframe');
17523 document.body.appendChild(frame);
17524
17525 frame.contentWindow.document.open("text/html", "replace");
17526 frame.contentWindow.document.write('sep=,\r\n' + csvContent);
17527 frame.contentWindow.document.close();
17528 frame.contentWindow.focus();
17529 frame.contentWindow.document.execCommand('SaveAs', true, fileName);
17530
17531 document.body.removeChild(frame);
17532 return true;
20032 downloadFile: function (fileName, csvContent, columnSeparator, exporterOlderExcelCompatibility, exporterIsExcelCompatible) {
20033 var D = document,
20034 a = D.createElement('a'),
20035 strMimeType = 'application/octet-stream;charset=utf-8',
20036 rawFile,
20037 ieVersion = this.isIE();
20038
20039 if (exporterIsExcelCompatible) {
20040 csvContent = 'sep=' + columnSeparator + '\r\n' + csvContent;
1753320041 }
1753420042
1753520043 // IE10+
1754220050 );
1754320051 }
1754420052
17545 //html5 A[download]
20053 if (ieVersion) {
20054 var frame = D.createElement('iframe');
20055
20056 document.body.appendChild(frame);
20057
20058 frame.contentWindow.document.open('text/html', 'replace');
20059 frame.contentWindow.document.write(csvContent);
20060 frame.contentWindow.document.close();
20061 frame.contentWindow.focus();
20062 frame.contentWindow.document.execCommand('SaveAs', true, fileName);
20063
20064 document.body.removeChild(frame);
20065 return true;
20066 }
20067
20068 // html5 A[download]
1754620069 if ('download' in a) {
1754720070 var blob = new Blob(
1754820071 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
1755120074 rawFile = URL.createObjectURL(blob);
1755220075 a.setAttribute('download', fileName);
1755320076 } else {
17554 rawFile = 'data:' + strMimeType + ',' + encodeURIComponent(csvContent);
20077 rawFile = 'data: ' + strMimeType + ',' + encodeURIComponent(csvContent);
1755520078 a.setAttribute('target', '_blank');
1755620079 }
1755720080
1759120114 */
1759220115 pdfExport: function (grid, rowTypes, colTypes) {
1759320116 var self = this;
20117
1759420118 this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function () {
17595 var exportColumnHeaders = self.getColumnHeaders(grid, colTypes);
17596 var exportData = self.getData(grid, rowTypes, colTypes);
17597 var docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
17598
17599 if (self.isIE()) {
20119 var exportColumnHeaders = self.getColumnHeaders(grid, colTypes),
20120 exportData = self.getData(grid, rowTypes, colTypes),
20121 docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
20122
20123 if (self.isIE() || navigator.appVersion.indexOf('Edge') !== -1) {
1760020124 self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
1760120125 } else {
1760220126 pdfMake.createPdf(docDefinition).open();
1761820142 * and get a blob from
1761920143 */
1762020144 downloadPDF: function (fileName, docDefinition) {
17621 var D = document;
17622 var a = D.createElement('a');
17623 var strMimeType = 'application/octet-stream;charset=utf-8';
17624 var rawFile;
17625 var ieVersion;
17626
17627 ieVersion = this.isIE();
20145 var D = document,
20146 a = D.createElement('a'),
20147 ieVersion;
20148
20149 ieVersion = this.isIE(); // This is now a boolean value
1762820150 var doc = pdfMake.createPdf(docDefinition);
1762920151 var blob;
1763020152
1763120153 doc.getBuffer( function (buffer) {
1763220154 blob = new Blob([buffer]);
17633
17634 if (ieVersion && ieVersion < 10) {
17635 var frame = D.createElement('iframe');
17636 document.body.appendChild(frame);
17637
17638 frame.contentWindow.document.open("text/html", "replace");
17639 frame.contentWindow.document.write(blob);
17640 frame.contentWindow.document.close();
17641 frame.contentWindow.focus();
17642 frame.contentWindow.document.execCommand('SaveAs', true, fileName);
17643
17644 document.body.removeChild(frame);
17645 return true;
17646 }
1764720155
1764820156 // IE10+
1764920157 if (navigator.msSaveBlob) {
1765020158 return navigator.msSaveBlob(
1765120159 blob, fileName
1765220160 );
20161 }
20162
20163 // Previously: && ieVersion < 10
20164 // ieVersion now returns a boolean for the
20165 // sake of sanity. We just check `msSaveBlob` first.
20166 if (ieVersion) {
20167 var frame = D.createElement('iframe');
20168 document.body.appendChild(frame);
20169
20170 frame.contentWindow.document.open('text/html', 'replace');
20171 frame.contentWindow.document.write(blob);
20172 frame.contentWindow.document.close();
20173 frame.contentWindow.focus();
20174 frame.contentWindow.document.execCommand('SaveAs', true, fileName);
20175
20176 document.body.removeChild(frame);
20177 return true;
1765320178 }
1765420179 });
1765520180 },
1769820223 defaultStyle: grid.options.exporterPdfDefaultStyle
1769920224 };
1770020225
17701 if ( grid.options.exporterPdfLayout ){
20226 if ( grid.options.exporterPdfLayout ) {
1770220227 docDefinition.layout = grid.options.exporterPdfLayout;
1770320228 }
1770420229
17705 if ( grid.options.exporterPdfHeader ){
20230 if ( grid.options.exporterPdfHeader ) {
1770620231 docDefinition.header = grid.options.exporterPdfHeader;
1770720232 }
1770820233
17709 if ( grid.options.exporterPdfFooter ){
20234 if ( grid.options.exporterPdfFooter ) {
1771020235 docDefinition.footer = grid.options.exporterPdfFooter;
1771120236 }
1771220237
17713 if ( grid.options.exporterPdfCustomFormatter ){
20238 if ( grid.options.exporterPdfCustomFormatter ) {
1771420239 docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
1771520240 }
1771620241 return docDefinition;
1774120266 */
1774220267 calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
1774320268 var baseGridWidth = 0;
17744 exportHeaders.forEach( function(value){
17745 if (typeof(value.width) === 'number'){
20269
20270 exportHeaders.forEach(function(value) {
20271 if (typeof(value.width) === 'number') {
1774620272 baseGridWidth += value.width;
1774720273 }
1774820274 });
1774920275
1775020276 var extraColumns = 0;
17751 exportHeaders.forEach( function(value){
17752 if (value.width === '*'){
20277
20278 exportHeaders.forEach(function(value) {
20279 if (value.width === '*') {
1775320280 extraColumns += 100;
1775420281 }
1775520282 if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
1776520292 return exportHeaders.map(function( header ) {
1776620293 return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
1776720294 });
17768
1776920295 },
1777020296
1777120297 /**
1779820324 */
1779920325 formatFieldAsPdfString: function (field) {
1780020326 var returnVal;
20327
1780120328 if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
1780220329 returnVal = '';
1780320330 } else if (typeof(field.value) === 'number') {
1780620333 returnVal = (field.value ? 'TRUE' : 'FALSE') ;
1780720334 } else if (typeof(field.value) === 'string') {
1780820335 returnVal = field.value.replace(/"/g,'""');
20336 } else if (field.value instanceof Date) {
20337 returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
20338 } else if (typeof(field.value) === 'object') {
20339 returnVal = field.value;
1780920340 } else {
1781020341 returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
1781120342 }
1781220343
17813 if (field.alignment && typeof(field.alignment) === 'string' ){
20344 if (field.alignment && typeof(field.alignment) === 'string' ) {
1781420345 returnVal = { text: returnVal, alignment: field.alignment };
1781520346 }
1781620347
1781720348 return returnVal;
20349 },
20350
20351 /**
20352 * @ngdoc function
20353 * @name formatAsExcel
20354 * @methodOf ui.grid.exporter.service:uiGridExporterService
20355 * @description Formats the column headers and data as a excel,
20356 * and sends that data to the user
20357 * @param {array} exportColumnHeaders an array of column headers,
20358 * where each header is an object with name, width and maybe alignment
20359 * @param {array} exportData an array of rows, where each row is
20360 * an array of column data
20361 * @param {string} separator a string that represents the separator to be used in the csv file
20362 * @returns {string} csv the formatted excel as a string
20363 */
20364 formatAsExcel: function (exportColumnHeaders, exportData, workbook, sheet, docDefinition) {
20365 var bareHeaders = exportColumnHeaders.map(function(header) {return { value: header.displayName };});
20366
20367 var sheetData = [];
20368 var headerData = [];
20369 for (var i = 0; i < bareHeaders.length; i++) {
20370 // TODO - probably need callback to determine header value and header styling
20371 var exportStyle = 'header';
20372 switch (exportColumnHeaders[i].align) {
20373 case 'center':
20374 exportStyle = 'headerCenter';
20375 break;
20376 case 'right':
20377 exportStyle = 'headerRight';
20378 break;
20379 }
20380 var metadata = (docDefinition.styles && docDefinition.styles[exportStyle]) ? {style: docDefinition.styles[exportStyle].id} : null;
20381 headerData.push({value: bareHeaders[i].value, metadata: metadata});
20382 }
20383 sheetData.push(headerData);
20384
20385 var result = exportData.map(this.formatRowAsExcel(this, workbook, sheet));
20386 for (var j = 0; j<result.length; j++) {
20387 sheetData.push(result[j]);
20388 }
20389 return sheetData;
20390 },
20391
20392 /**
20393 * @ngdoc function
20394 * @name formatRowAsExcel
20395 * @methodOf ui.grid.exporter.service:uiGridExporterService
20396 * @description Renders a single field as a csv field, including
20397 * quotes around the value
20398 * @param {exporterService} exporter pass in exporter
20399 * @param {array} row the row to be turned into a excel string
20400 * @returns {array} array of cell objects (i.e. {value: x, metadata: y})
20401 */
20402 formatRowAsExcel: function (exporter, workbook, sheet) {
20403 return function (row) {
20404 var values = [];
20405 for (var i = 0; i<row.length; i++) {
20406 var value = exporter.formatFieldAsExcel(row[i], workbook, sheet);
20407 values.push({value: value, metadata: row[i].metadata});
20408 }
20409 return values;
20410 };
20411 },
20412
20413 /**
20414 * @ngdoc function
20415 * @name formatFieldAsExcel
20416 * @methodOf ui.grid.exporter.service:uiGridExporterService
20417 * @description Renders a single field as a csv field, including
20418 * quotes around the value
20419 * @param {field} field the field to be turned into a csv string,
20420 * may be of any type
20421 * @returns {string} a excel-ified version of the field
20422 */
20423 formatFieldAsExcel: function (field, workbook, sheet, formatters) {
20424 if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
20425 return '';
20426 }
20427 if (typeof(field.value) === 'number') {
20428 return field.value;
20429 }
20430 if (typeof(field.value) === 'boolean') {
20431 return (field.value ? 'TRUE' : 'FALSE') ;
20432 }
20433 if (typeof(field.value) === 'string') {
20434 return field.value.replace(/"/g,'""');
20435 }
20436
20437 return JSON.stringify(field.value);
20438 },
20439
20440 prepareAsExcel: function(grid, workbook, sheet) {
20441 var docDefinition = {
20442 styles: {
20443
20444 }
20445 };
20446
20447 if ( grid.options.exporterExcelCustomFormatters ) {
20448 docDefinition = grid.options.exporterExcelCustomFormatters( grid, workbook, docDefinition );
20449 }
20450 if ( grid.options.exporterExcelHeader ) {
20451 if (angular.isFunction( grid.options.exporterExcelHeader )) {
20452 grid.options.exporterExcelHeader(grid, workbook, sheet, docDefinition);
20453 } else {
20454 var headerText = grid.options.exporterExcelHeader.text;
20455 var style = grid.options.exporterExcelHeader.style;
20456 sheet.data.push([{value: headerText, metadata: {style: docDefinition.styles[style].id}}]);
20457 }
20458 }
20459
20460 return docDefinition;
20461 },
20462
20463 excelExport: function (grid, rowTypes, colTypes) {
20464 var self = this;
20465 this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function() {
20466 var exportColumnHeaders = grid.options.showHeader ? self.getColumnHeaders(grid, colTypes) : [];
20467
20468 var workbook = new ExcelBuilder.Workbook();
20469 var aName = grid.options.exporterExcelSheetName ? grid.options.exporterExcelSheetName : 'Sheet1';
20470 var sheet = new ExcelBuilder.Worksheet({name: aName});
20471 workbook.addWorksheet(sheet);
20472 var docDefinition = self.prepareAsExcel(grid, workbook, sheet);
20473
20474 // The standard column width in Microsoft Excel 2000 is 8.43 characters based on fixed-width Courier font
20475 // Width of 10 in excel is 75 pixels
20476 var colWidths = [];
20477 var startDataIndex = grid.treeBase ? grid.treeBase.numberLevels : (grid.enableRowSelection ? 1 : 0);
20478 for (var i = startDataIndex; i < grid.columns.length; i++) {
20479 if (grid.columns[i].field !== uiGridExporterConstants.rowHeaderColName &&
20480 grid.columns[i].field !== uiGridExporterConstants.selectionRowHeaderColName) {
20481
20482 colWidths.push({width: (grid.columns[i].drawnWidth / grid.options.exporterColumnScaleFactor)});
20483 }
20484 }
20485 sheet.setColumns(colWidths);
20486
20487 var exportData = self.getData(grid, rowTypes, colTypes, grid.options.exporterFieldApplyFilters);
20488
20489 var excelContent = self.formatAsExcel(exportColumnHeaders, exportData, workbook, sheet, docDefinition);
20490 sheet.setData(sheet.data.concat(excelContent));
20491
20492 ExcelBuilder.Builder.createFile(workbook, {type: 'blob'}).then(function(result) {
20493 self.downloadFile (grid.options.exporterExcelFilename, result, grid.options.exporterCsvColumnSeparator,
20494 grid.options.exporterOlderExcelCompatibility);
20495 });
20496 });
1781820497 }
1781920498 };
1782020499
20500 function getDisplayName(grid, gridCol) {
20501 if (grid.options.exporterHeaderFilter) {
20502 return grid.options.exporterHeaderFilterUseName ?
20503 grid.options.exporterHeaderFilter(gridCol.name) :
20504 grid.options.exporterHeaderFilter(gridCol.displayName);
20505 }
20506
20507 return gridCol.headerCellFilter ?
20508 $filter(gridCol.headerCellFilter)(gridCol.displayName) :
20509 gridCol.displayName;
20510 }
20511
20512 function defaultExporterFieldCallback(grid, row, col, value) {
20513 // fix to handle cases with 'number : 1' or 'date:MM-dd-YYYY', etc.. We needed to split the string
20514 if (col.cellFilter) {
20515 var args, filter, arg1, arg2;
20516 // remove space, single/double to mantein retro-compatibility
20517 args = col.cellFilter.replace(/[\'\"\s]/g, "").split(':');
20518 filter = args[0] ? args[0] : null;
20519 arg1 = args[1] ? args[1] : null;
20520 arg2 = args[2] ? args[2] : null;
20521 return $filter(filter)(value, arg1, arg2);
20522 } else {
20523 return value;
20524 }
20525 }
20526
1782120527 return service;
17822
1782320528 }
1782420529 ]);
1782520530
1797020675 */
1797120676 module.service('uiGridGroupingService', ['$q', 'uiGridGroupingConstants', 'gridUtil', 'rowSorter', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'uiGridTreeBaseService',
1797220677 function ($q, uiGridGroupingConstants, gridUtil, rowSorter, GridRow, gridClassFactory, i18nService, uiGridConstants, uiGridTreeBaseService) {
17973
1797420678 var service = {
17975
1797620679 initializeGrid: function (grid, $scope) {
1797720680 uiGridTreeBaseService.initializeGrid( grid, $scope );
1797820681
17979 //add feature namespace and any properties to grid for needed
20682 // add feature namespace and any properties to grid for needed
1798020683 /**
1798120684 * @ngdoc object
1798220685 * @name ui.grid.grouping.grid:grouping
1804520748 * @description raised whenever aggregation is changed, added or removed from a column
1804620749 *
1804720750 * <pre>
18048 * gridApi.grouping.on.aggregationChanged(scope,function(col){})
20751 * gridApi.grouping.on.aggregationChanged(scope,function(col) {})
1804920752 * </pre>
18050 * @param {gridCol} col the column which on which aggregation changed. The aggregation
20753 * @param {GridColumn} col the column which on which aggregation changed. The aggregation
1805120754 * type is available as `col.treeAggregation.type`
1805220755 */
1805320756 aggregationChanged: {},
1805820761 * @description raised whenever the grouped columns changes
1805920762 *
1806020763 * <pre>
18061 * gridApi.grouping.on.groupingChanged(scope,function(col){})
20764 * gridApi.grouping.on.groupingChanged(scope,function(col) {})
1806220765 * </pre>
18063 * @param {gridCol} col the column which on which grouping changed. The new grouping is
20766 * @param {GridColumn} col the column which on which grouping changed. The new grouping is
1806420767 * available as `col.grouping`
1806520768 */
1806620769 groupingChanged: {}
1810220805 delete aggregation.col;
1810320806 });
1810420807
18105 grouping.aggregations = grouping.aggregations.filter( function( aggregation ){
20808 grouping.aggregations = grouping.aggregations.filter( function( aggregation ) {
1810620809 return !aggregation.aggregation.source || aggregation.aggregation.source !== 'grouping';
1810720810 });
1810820811
18109 if ( getExpanded ){
20812 if ( getExpanded ) {
1811020813 grouping.rowExpandedStates = service.getRowExpandedStates( grid.grouping.groupingHeaderCache );
1811120814 }
1811220815
1813920842 *
1814020843 * @param {string} columnName the name of the column we want to group
1814120844 */
18142 groupColumn: function( columnName ) {
20845 groupColumn: function(columnName) {
1814320846 var column = grid.getColumn(columnName);
20847
1814420848 service.groupColumn(grid, column);
1814520849 },
1814620850
1815720861 *
1815820862 * @param {string} columnName the name of the column we want to ungroup
1815920863 */
18160 ungroupColumn: function( columnName ) {
20864 ungroupColumn: function(columnName) {
1816120865 var column = grid.getColumn(columnName);
20866
1816220867 service.ungroupColumn(grid, column);
1816320868 },
1816420869
1818420889 * being removed
1818520890 *
1818620891 * @param {string} columnName the column we want to aggregate
18187 * @param {string} or {function} aggregationDef one of the recognised types
20892 * @param {string|function} aggregationDef one of the recognised types
1818820893 * from uiGridGroupingConstants or a custom aggregation function.
1818920894 * @param {string} aggregationLabel (optional) The label to use for this aggregation.
1819020895 */
18191 aggregateColumn: function( columnName, aggregationDef, aggregationLabel){
20896 aggregateColumn: function(columnName, aggregationDef, aggregationLabel) {
1819220897 var column = grid.getColumn(columnName);
18193 service.aggregateColumn( grid, column, aggregationDef, aggregationLabel);
20898
20899 service.aggregateColumn(grid, column, aggregationDef, aggregationLabel);
1819420900 }
18195
1819620901 }
1819720902 }
1819820903 };
1820120906
1820220907 grid.api.registerMethodsFromObject(publicApi.methods);
1820320908
18204 grid.api.core.on.sortChanged( $scope, service.tidyPriorities);
18205
20909 grid.api.core.on.sortChanged($scope, service.tidyPriorities);
1820620910 },
1820720911
1820820912 defaultGridOptions: function (gridOptions) {
18209 //default option to true unless it was explicitly set to false
20913 // default option to true unless it was explicitly set to false
1821020914 /**
1821120915 * @ngdoc object
1821220916 * @name ui.grid.grouping.api:GridOptions
1823120935 * @description shows counts on the groupHeader rows. Not that if you are using a cellFilter or a
1823220936 * sortingAlgorithm which relies on a specific format or data type, showing counts may cause that
1823320937 * to break, since the group header rows will always be a string with groupingShowCounts enabled.
18234 * <br/>Defaults to true except on columns of type 'date'
20938 * <br/>Defaults to true except on columns of types 'date' and 'object'
1823520939 */
1823620940 gridOptions.groupingShowCounts = gridOptions.groupingShowCounts !== false;
1823720941
1826220966 * @description Sets the grouping defaults based on the columnDefs
1826320967 *
1826420968 * @param {object} colDef columnDef we're basing on
18265 * @param {GridCol} col the column we're to update
20969 * @param {GridColumn} col the column we're to update
1826620970 * @param {object} gridOptions the options we should use
1826720971 * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
1826820972 */
1828220986 * @description Enable grouping on this column
1828320987 * <br/>Defaults to true.
1828420988 */
18285 if (colDef.enableGrouping === false){
20989 if (colDef.enableGrouping === false) {
1828620990 return;
1828720991 }
1828820992
1831521019
1831621020 if ( typeof(col.grouping) === 'undefined' && typeof(colDef.grouping) !== 'undefined') {
1831721021 col.grouping = angular.copy(colDef.grouping);
18318 if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ){
21022 if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ) {
1831921023 col.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
1832021024 col.treeAggregationFinalizerFn = service.groupedFinalizerFn;
1832121025 }
18322 } else if (typeof(col.grouping) === 'undefined'){
21026 } else if (typeof(col.grouping) === 'undefined') {
1832321027 col.grouping = {};
1832421028 }
1832521029
18326 if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0){
21030 if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0) {
1832721031 col.suppressRemoveSort = true;
1832821032 }
1832921033
1836721071 };
1836821072
1836921073 // generic adder for the aggregation menus, which follow a pattern
18370 var addAggregationMenu = function(type, title){
21074 var addAggregationMenu = function(type, title) {
1837121075 title = title || i18nService.get().grouping['aggregate_' + type] || type;
1837221076 var menuItem = {
1837321077 name: 'ui.grid.grouping.aggregate' + type,
1839421098 * @description Show the grouping (group and ungroup items) menu on this column
1839521099 * <br/>Defaults to true.
1839621100 */
18397 if ( col.colDef.groupingShowGroupingMenu !== false ){
21101 if ( col.colDef.groupingShowGroupingMenu !== false ) {
1839821102 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.group')) {
1839921103 col.menuItems.push(groupColumn);
1840021104 }
1841221116 * @description Show the aggregation menu on this column
1841321117 * <br/>Defaults to true.
1841421118 */
18415 if ( col.colDef.groupingShowAggregationMenu !== false ){
18416 angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name){
21119 if ( col.colDef.groupingShowAggregationMenu !== false ) {
21120 angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name) {
1841721121 addAggregationMenu(name);
1841821122 });
18419 angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name){
21123 angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name) {
1842021124 addAggregationMenu(name, aggregationDef.menuTitle);
1842121125 });
1842221126
1844021144 * @returns {array} updated columns array
1844121145 */
1844221146 groupingColumnProcessor: function( columns, rows ) {
18443 var grid = this;
18444
1844521147 columns = service.moveGroupColumns(this, columns, rows);
1844621148 return columns;
1844721149 },
1845321155 * @description Used on group columns to display the rendered value and optionally
1845421156 * display the count of rows.
1845521157 *
18456 * @param {aggregation} the aggregation entity for a grouped column
21158 * @param {aggregation} aggregation The aggregation entity for a grouped column
1845721159 */
18458 groupedFinalizerFn: function( aggregation ){
21160 groupedFinalizerFn: function( aggregation ) {
1845921161 var col = this;
1846021162
1846121163 if ( typeof(aggregation.groupVal) !== 'undefined') {
1846221164 aggregation.rendered = aggregation.groupVal;
18463 if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' ){
21165 if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' && col.colDef.type !== 'object' ) {
1846421166 aggregation.rendered += (' (' + aggregation.value + ')');
1846521167 }
1846621168 } else {
1848121183 *
1848221184 * @param {Grid} grid grid object
1848321185 * @param {array} columns the columns that we should process/move
18484 * @param {array} rows the grid rows
1848521186 * @returns {array} updated columns
1848621187 */
18487 moveGroupColumns: function( grid, columns, rows ){
18488 if ( grid.options.moveGroupColumns === false){
21188 moveGroupColumns: function( grid, columns ) {
21189 if ( grid.options.moveGroupColumns === false) {
1848921190 return columns;
1849021191 }
1849121192
18492 columns.forEach( function(column, index){
21193 columns.forEach(function(column, index) {
1849321194 // position used to make stable sort in moveGroupColumns
1849421195 column.groupingPosition = index;
1849521196 });
1849621197
18497 columns.sort(function(a, b){
21198 columns.sort(function(a, b) {
1849821199 var a_group, b_group;
18499 if (a.isRowHeader){
18500 a_group = -1000;
18501 }
18502 else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0){
21200
21201 if (a.isRowHeader) {
21202 a_group = a.headerPriority;
21203 }
21204 else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0) {
1850321205 a_group = null;
18504 } else {
21206 }
21207 else {
1850521208 a_group = a.grouping.groupPriority;
1850621209 }
1850721210
18508 if (b.isRowHeader){
18509 b_group = -1000;
18510 }
18511 else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0){
21211 if (b.isRowHeader) {
21212 b_group = b.headerPriority;
21213 }
21214 else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0) {
1851221215 b_group = null;
18513 } else {
21216 }
21217 else {
1851421218 b_group = b.grouping.groupPriority;
1851521219 }
1851621220
1852221226 return a.groupingPosition - b.groupingPosition;
1852321227 });
1852421228
18525 columns.forEach( function(column, index) {
21229 columns.forEach( function(column) {
1852621230 delete column.groupingPosition;
1852721231 });
1852821232
1854121245 * move is handled in a columnProcessor, so gets called as part of refresh
1854221246 *
1854321247 * @param {Grid} grid grid object
18544 * @param {GridCol} column the column we want to group
21248 * @param {GridColumn} column the column we want to group
1854521249 */
18546 groupColumn: function( grid, column){
18547 if ( typeof(column.grouping) === 'undefined' ){
21250 groupColumn: function( grid, column) {
21251 if ( typeof(column.grouping) === 'undefined' ) {
1854821252 column.grouping = {};
1854921253 }
1855021254
1855221256 var existingGrouping = service.getGrouping( grid );
1855321257 column.grouping.groupPriority = existingGrouping.grouping.length;
1855421258
21259 // save sort in order to restore it when column is ungrouped
21260 column.previousSort = angular.copy(column.sort);
21261
1855521262 // add sort if not present
18556 if ( !column.sort ){
21263 if ( !column.sort ) {
1855721264 column.sort = { direction: uiGridConstants.ASC };
18558 } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ){
21265 } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ) {
1855921266 column.sort.direction = uiGridConstants.ASC;
1856021267 }
1856121268
1858321290 * move is handled in a columnProcessor, so gets called as part of refresh
1858421291 *
1858521292 * @param {Grid} grid grid object
18586 * @param {GridCol} column the column we want to ungroup
21293 * @param {GridColumn} column the column we want to ungroup
1858721294 */
18588 ungroupColumn: function( grid, column){
18589 if ( typeof(column.grouping) === 'undefined' ){
21295 ungroupColumn: function( grid, column) {
21296 if ( typeof(column.grouping) === 'undefined' ) {
1859021297 return;
1859121298 }
1859221299
1859421301 delete column.treeAggregation;
1859521302 delete column.customTreeAggregationFinalizer;
1859621303
21304 if (column.previousSort) {
21305 column.sort = column.previousSort;
21306 delete column.previousSort;
21307 }
21308
1859721309 service.tidyPriorities( grid );
1859821310
1859921311 grid.api.grouping.raise.groupingChanged(column);
21312 grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
1860021313
1860121314 grid.queueGridRefresh();
1860221315 },
1860921322 * column is currently grouped then it removes the grouping first.
1861021323 *
1861121324 * @param {Grid} grid grid object
18612 * @param {GridCol} column the column we want to aggregate
18613 * @param {string} one of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
21325 * @param {GridColumn} column the column we want to aggregate
21326 * @param {string} aggregationType of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
1861421327 */
18615 aggregateColumn: function( grid, column, aggregationType){
18616
18617 if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
21328 aggregateColumn: function( grid, column, aggregationType) {
21329 if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0) {
1861821330 service.ungroupColumn( grid, column );
1861921331 }
1862021332
1862121333 var aggregationDef = {};
18622 if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ){
21334
21335 if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ) {
1862321336 aggregationDef = grid.options.treeCustomAggregations[aggregationType];
18624 } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ){
21337 } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ) {
1862521338 aggregationDef = uiGridTreeBaseService.nativeAggregations()[aggregationType];
1862621339 }
1862721340
1864521358 * @param {Grid} grid grid object
1864621359 * @param {object} config the config we want to set, same format as that returned by getGrouping
1864721360 */
18648 setGrouping: function ( grid, config ){
18649 if ( typeof(config) === 'undefined' ){
21361 setGrouping: function ( grid, config ) {
21362 if ( typeof(config) === 'undefined' ) {
1865021363 return;
1865121364 }
1865221365
1865321366 // first remove any existing grouping
1865421367 service.clearGrouping(grid);
1865521368
18656 if ( config.grouping && config.grouping.length && config.grouping.length > 0 ){
21369 if ( config.grouping && config.grouping.length && config.grouping.length > 0 ) {
1865721370 config.grouping.forEach( function( group ) {
1865821371 var col = grid.getColumn(group.colName);
1865921372
1866321376 });
1866421377 }
1866521378
18666 if ( config.aggregations && config.aggregations.length ){
21379 if ( config.aggregations && config.aggregations.length ) {
1866721380 config.aggregations.forEach( function( aggregation ) {
1866821381 var col = grid.getColumn(aggregation.colName);
1866921382
1867321386 });
1867421387 }
1867521388
18676 if ( config.rowExpandedStates ){
21389 if ( config.rowExpandedStates ) {
1867721390 service.applyRowExpandedStates( grid.grouping.groupingHeaderCache, config.rowExpandedStates );
1867821391 }
1867921392 },
1869121404 clearGrouping: function( grid ) {
1869221405 var currentGrouping = service.getGrouping(grid);
1869321406
18694 if ( currentGrouping.grouping.length > 0 ){
21407 if ( currentGrouping.grouping.length > 0 ) {
1869521408 currentGrouping.grouping.forEach( function( group ) {
18696 if (!group.col){
21409 if (!group.col) {
1869721410 // should have a group.colName if there's no col
1869821411 group.col = grid.getColumn(group.colName);
1869921412 }
1870121414 });
1870221415 }
1870321416
18704 if ( currentGrouping.aggregations.length > 0 ){
18705 currentGrouping.aggregations.forEach( function( aggregation ){
18706 if (!aggregation.col){
21417 if ( currentGrouping.aggregations.length > 0 ) {
21418 currentGrouping.aggregations.forEach( function( aggregation ) {
21419 if (!aggregation.col) {
1870721420 // should have a group.colName if there's no col
1870821421 aggregation.col = grid.getColumn(aggregation.colName);
1870921422 }
1872421437 *
1872521438 * @param {Grid} grid grid object
1872621439 */
18727 tidyPriorities: function( grid ){
21440 tidyPriorities: function( grid ) {
1872821441 // if we're called from sortChanged, grid is in this, not passed as param, the param can be a column or undefined
1872921442 if ( ( typeof(grid) === 'undefined' || typeof(grid.grid) !== 'undefined' ) && typeof(this.grid) !== 'undefined' ) {
1873021443 grid = this.grid;
1873121444 }
1873221445
18733 var groupArray = [];
18734 var sortArray = [];
18735
18736 grid.columns.forEach( function(column, index){
18737 if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
21446 var groupArray = [],
21447 sortArray = [];
21448
21449 grid.columns.forEach( function(column, index) {
21450 if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0) {
1873821451 groupArray.push(column);
18739 } else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0){
21452 }
21453 else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0) {
1874021454 sortArray.push(column);
1874121455 }
1874221456 });
1874321457
18744 groupArray.sort(function(a, b){ return a.grouping.groupPriority - b.grouping.groupPriority; });
18745 groupArray.forEach( function(column, index){
21458 groupArray.sort(function(a, b) { return a.grouping.groupPriority - b.grouping.groupPriority; });
21459 groupArray.forEach( function(column, index) {
1874621460 column.grouping.groupPriority = index;
1874721461 column.suppressRemoveSort = true;
18748 if ( typeof(column.sort) === 'undefined'){
21462 if ( typeof(column.sort) === 'undefined') {
1874921463 column.sort = {};
1875021464 }
1875121465 column.sort.priority = index;
1875221466 });
1875321467
1875421468 var i = groupArray.length;
18755 sortArray.sort(function(a, b){ return a.sort.priority - b.sort.priority; });
18756 sortArray.forEach( function(column, index){
21469
21470 sortArray.sort(function(a, b) { return a.sort.priority - b.sort.priority; });
21471 sortArray.forEach(function(column) {
1875721472 column.sort.priority = i;
1875821473 column.suppressRemoveSort = column.colDef.suppressRemoveSort;
1875921474 i++;
1876021475 });
1876121476 },
18762
1876321477
1876421478 /**
1876521479 * @ngdoc function
1879421508 * @returns {array} the updated rows, including our new group rows
1879521509 */
1879621510 groupRows: function( renderableRows ) {
18797 if (renderableRows.length === 0){
21511 if (renderableRows.length === 0) {
1879821512 return renderableRows;
1879921513 }
1880021514
1881021524 var fieldValue = grid.getCellValue(row, groupFieldState.col);
1881121525
1881221526 // look for change of value - and insert a header
18813 if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ){
21527 if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ) {
1881421528 service.insertGroupHeader( grid, renderableRows, i, processingState, stateIndex );
1881521529 i++;
1881621530 }
1881821532
1881921533 // use a for loop because it's tolerant of the array length changing whilst we go - we can
1882021534 // manipulate the iterator when we insert groupHeader rows
18821 for (var i = 0; i < renderableRows.length; i++ ){
21535 for (var i = 0; i < renderableRows.length; i++ ) {
1882221536 var row = renderableRows[i];
1882321537
18824 if ( row.visible ){
21538 if ( row.visible ) {
1882521539 processingState.forEach( updateProcessingState );
1882621540 }
1882721541 }
1884221556 * @returns {array} an array in the format described in the groupRows method,
1884321557 * initialised with blank values
1884421558 */
18845 initialiseProcessingState: function( grid ){
21559 initialiseProcessingState: function( grid ) {
1884621560 var processingState = [];
1884721561 var columnSettings = service.getGrouping( grid );
1884821562
18849 columnSettings.grouping.forEach( function( groupItem, index){
21563 columnSettings.grouping.forEach( function( groupItem, index) {
1885021564 processingState.push({
1885121565 fieldName: groupItem.field,
1885221566 col: groupItem.col,
1886921583 * @param {Grid} grid grid object
1887021584 * @returns {array} an array of the group fields, in order of priority
1887121585 */
18872 getGrouping: function( grid ){
18873 var groupArray = [];
18874 var aggregateArray = [];
21586 getGrouping: function( grid ) {
21587 var groupArray = [],
21588 aggregateArray = [];
1887521589
1887621590 // get all the grouping
18877 grid.columns.forEach( function(column, columnIndex){
18878 if ( column.grouping ){
18879 if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
21591 grid.columns.forEach(function(column) {
21592 if ( column.grouping ) {
21593 if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0) {
1888021594 groupArray.push({ field: column.field, col: column, groupPriority: column.grouping.groupPriority, grouping: column.grouping });
1888121595 }
1888221596 }
18883 if ( column.treeAggregation && column.treeAggregation.type ){
21597 if ( column.treeAggregation && column.treeAggregation.type ) {
1888421598 aggregateArray.push({ field: column.field, col: column, aggregation: column.treeAggregation });
1888521599 }
1888621600 });
1888721601
1888821602 // sort grouping into priority order
18889 groupArray.sort( function(a, b){
21603 groupArray.sort( function(a, b) {
1889021604 return a.groupPriority - b.groupPriority;
1889121605 });
1889221606
1891921633 */
1892021634 insertGroupHeader: function( grid, renderableRows, rowIndex, processingState, stateIndex ) {
1892121635 // set the value that caused the end of a group into the header row and the processing state
18922 var fieldName = processingState[stateIndex].fieldName;
18923 var col = processingState[stateIndex].col;
18924
18925 var newValue = grid.getCellValue(renderableRows[rowIndex], col);
18926 var newDisplayValue = newValue;
21636 var col = processingState[stateIndex].col,
21637 newValue = grid.getCellValue(renderableRows[rowIndex], col),
21638 newDisplayValue = newValue;
21639
1892721640 if ( typeof(newValue) === 'undefined' || newValue === null ) {
1892821641 newDisplayValue = grid.options.groupingNullLabel;
1892921642 }
1893021643
21644 function getKeyAsValueForCacheMap(key) {
21645 return angular.isObject(key) ? JSON.stringify(key) : key;
21646 }
21647
1893121648 var cacheItem = grid.grouping.oldGroupingHeaderCache;
18932 for ( var i = 0; i < stateIndex; i++ ){
18933 if ( cacheItem && cacheItem[processingState[i].currentValue] ){
18934 cacheItem = cacheItem[processingState[i].currentValue].children;
21649
21650 for ( var i = 0; i < stateIndex; i++ ) {
21651 if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)] ) {
21652 cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
1893521653 }
1893621654 }
1893721655
1893821656 var headerRow;
18939 if ( cacheItem && cacheItem[newValue]){
18940 headerRow = cacheItem[newValue].row;
21657
21658 if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(newValue)]) {
21659 headerRow = cacheItem[getKeyAsValueForCacheMap(newValue)].row;
1894121660 headerRow.entity = {};
1894221661 } else {
1894321662 headerRow = new GridRow( {}, null, grid );
1896321682
1896421683 // add our new header row to the cache
1896521684 cacheItem = grid.grouping.groupingHeaderCache;
18966 for ( i = 0; i < stateIndex; i++ ){
18967 cacheItem = cacheItem[processingState[i].currentValue].children;
18968 }
18969 cacheItem[newValue] = { row: headerRow, children: {} };
21685 for ( i = 0; i < stateIndex; i++ ) {
21686 cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
21687 }
21688 cacheItem[getKeyAsValueForCacheMap(newValue)] = { row: headerRow, children: {} };
1897021689 },
1897121690
1897221691
1897721696 * @description Set all processing states lower than the one that had a break in value to
1897821697 * no longer be initialised. Render the counts into the entity ready for display.
1897921698 *
18980 * @param {Grid} grid grid object
1898121699 * @param {array} processingState the current processing state
1898221700 * @param {number} stateIndex the processing state item that we were on when we triggered a new group header, all
1898321701 * processing states after this need to be finalised
1898421702 */
18985 finaliseProcessingState: function( processingState, stateIndex ){
18986 for ( var i = stateIndex; i < processingState.length; i++){
21703 finaliseProcessingState: function( processingState, stateIndex ) {
21704 for ( var i = stateIndex; i < processingState.length; i++) {
1898721705 processingState[i].initialised = false;
1898821706 processingState[i].currentRow = null;
1898921707 processingState[i].currentValue = null;
1901821736 * }
1901921737 * </pre>
1902021738 *
19021 * @param {Grid} grid grid object
19022 * @returns {hash} the expanded states as a hash
21739 * @param {object} treeChildren The tree children elements object
21740 * @returns {object} the expanded states as an object
1902321741 */
19024 getRowExpandedStates: function(treeChildren){
19025 if ( typeof(treeChildren) === 'undefined' ){
21742 getRowExpandedStates: function(treeChildren) {
21743 if ( typeof(treeChildren) === 'undefined' ) {
1902621744 return {};
1902721745 }
1902821746
1902921747 var newChildren = {};
1903021748
19031 angular.forEach( treeChildren, function( value, key ){
21749 angular.forEach( treeChildren, function( value, key ) {
1903221750 newChildren[key] = { state: value.row.treeNode.state };
19033 if ( value.children ){
21751 if ( value.children ) {
1903421752 newChildren[key].children = service.getRowExpandedStates( value.children );
1903521753 } else {
1903621754 newChildren[key].children = {};
1905321771 *
1905421772 * @param {object} currentNode can be grid.grouping.groupHeaderCache, or any of
1905521773 * the children of that hash
19056 * @returns {hash} expandedStates can be the full expanded states, or children
21774 * @param {object} expandedStates can be the full expanded states, or children
1905721775 * of that expanded states (which hopefully matches the subset of the groupHeaderCache)
1905821776 */
19059 applyRowExpandedStates: function( currentNode, expandedStates ){
19060 if ( typeof(expandedStates) === 'undefined' ){
21777 applyRowExpandedStates: function( currentNode, expandedStates ) {
21778 if ( typeof(expandedStates) === 'undefined' ) {
1906121779 return;
1906221780 }
1906321781
1906421782 angular.forEach(expandedStates, function( value, key ) {
19065 if ( currentNode[key] ){
21783 if ( currentNode[key] ) {
1906621784 currentNode[key].row.treeNode.state = value.state;
1906721785
19068 if (value.children && currentNode[key].children){
21786 if (value.children && currentNode[key].children) {
1906921787 service.applyRowExpandedStates( currentNode[key].children, value.children );
1907021788 }
1907121789 }
1911421832 </file>
1911521833 </example>
1911621834 */
19117 module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService', '$templateCache',
19118 function (uiGridGroupingConstants, uiGridGroupingService, $templateCache) {
21835 module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService',
21836 function (uiGridGroupingConstants, uiGridGroupingService) {
1911921837 return {
1912021838 replace: true,
1912121839 priority: 0,
1912421842 compile: function () {
1912521843 return {
1912621844 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
19127 if (uiGridCtrl.grid.options.enableGrouping !== false){
21845 if (uiGridCtrl.grid.options.enableGrouping !== false) {
1912821846 uiGridGroupingService.initializeGrid(uiGridCtrl.grid, $scope);
1912921847 }
1913021848 },
1920121919
1920221920 initializeGrid: function ($scope, grid) {
1920321921
19204 //add feature namespace and any properties to grid for needed state
21922 // add feature namespace and any properties to grid for needed state
1920521923 grid.importer = {
1920621924 $scope: $scope
1920721925 };
1924121959
1924221960 grid.api.registerMethodsFromObject(publicApi.methods);
1924321961
19244 if ( grid.options.enableImporter && grid.options.importerShowMenu ){
19245 if ( grid.api.core.addToGridMenu ){
21962 if ( grid.options.enableImporter && grid.options.importerShowMenu ) {
21963 if ( grid.api.core.addToGridMenu ) {
1924621964 service.addToMenu( grid );
1924721965 } else {
1924821966 // order of registration is not guaranteed, register in a little while
1924921967 $interval( function() {
19250 if (grid.api.core.addToGridMenu){
21968 if (grid.api.core.addToGridMenu) {
1925121969 service.addToMenu( grid );
1925221970 }
1925321971 }, 100, 1);
1925721975
1925821976
1925921977 defaultGridOptions: function (gridOptions) {
19260 //default option to true unless it was explicitly set to false
21978 // default option to true unless it was explicitly set to false
1926121979 /**
1926221980 * @ngdoc object
1926321981 * @name ui.grid.importer.api:GridOptions
1930422022 * of matching column names. A null value in any given position means "don't import this column"
1930522023 *
1930622024 * <pre>
19307 * gridOptions.importerProcessHeaders: function( headerArray ) {
22025 * gridOptions.importerProcessHeaders: function( grid, headerArray ) {
1930822026 * var myHeaderColumns = [];
1930922027 * var thisCol;
1931022028 * headerArray.forEach( function( value, index ) {
1937222090 * often the file content itself or the element that is in error
1937322091 *
1937422092 */
19375 if ( !gridOptions.importerErrorCallback || typeof(gridOptions.importerErrorCallback) !== 'function' ){
22093 if ( !gridOptions.importerErrorCallback || typeof(gridOptions.importerErrorCallback) !== 'function' ) {
1937622094 delete gridOptions.importerErrorCallback;
1937722095 }
1937822096
1947022188 },
1947122189 {
1947222190 templateUrl: 'ui-grid/importerMenuItemContainer',
19473 action: function ($event) {
22191 action: function () {
1947422192 this.grid.api.importer.importAFile( grid );
1947522193 },
1947622194 order: 151
1949022208 * javascript object
1949122209 */
1949222210 importThisFile: function ( grid, fileObject ) {
19493 if (!fileObject){
22211 if (!fileObject) {
1949422212 gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
1949522213 return;
1949622214 }
1949722215
1949822216 var reader = new FileReader();
1949922217
19500 switch ( fileObject.type ){
22218 switch ( fileObject.type ) {
1950122219 case 'application/json':
1950222220 reader.onload = service.importJsonClosure( grid );
1950322221 break;
1951822236 * The json data is imported into new objects of type `gridOptions.importerNewObject`,
1951922237 * and if the rowEdit feature is enabled the rows are marked as dirty
1952022238 * @param {Grid} grid the grid we want to import into
19521 * @param {FileObject} importFile the file that we want to import, as
19522 * a FileObject
22239 * @return {function} Function that receives the file that we want to import, as
22240 * a FileObject as an argument
1952322241 */
1952422242 importJsonClosure: function( grid ) {
19525 return function( importFile ){
19526 var newObjects = [];
19527 var newObject;
19528
19529 var importArray = service.parseJson( grid, importFile );
19530 if (importArray === null){
22243 return function( importFile ) {
22244 var newObjects = [],
22245 newObject,
22246 importArray = service.parseJson( grid, importFile );
22247
22248 if (importArray === null) {
1953122249 return;
1953222250 }
19533 importArray.forEach( function( value, index ) {
22251 importArray.forEach( function( value ) {
1953422252 newObject = service.newObject( grid );
1953522253 angular.extend( newObject, value );
1953622254 newObject = grid.options.importerObjectCallback( grid, newObject );
1953822256 });
1953922257
1954022258 service.addObjects( grid, newObjects );
19541
1954222259 };
1954322260 },
1954422261
1955422271 * a FileObject
1955522272 * @returns {array} array of objects from the imported json
1955622273 */
19557 parseJson: function( grid, importFile ){
22274 parseJson: function( grid, importFile ) {
1955822275 var loadedObjects;
22276
1955922277 try {
1956022278 loadedObjects = JSON.parse( importFile.target.result );
1956122279 } catch (e) {
1956322281 return;
1956422282 }
1956522283
19566 if ( !Array.isArray( loadedObjects ) ){
22284 if ( !Array.isArray( loadedObjects ) ) {
1956722285 service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
1956822286 return [];
1956922287 } else {
1958022298 * @description Creates a function that imports a csv file into the grid
1958122299 * (allowing it to be used in the reader.onload event)
1958222300 * @param {Grid} grid the grid that we want to import into
19583 * @param {FileObject} importFile the file that we want to import, as
22301 * @return {function} Function that receives the file that we want to import, as
1958422302 * a file object
1958522303 */
1958622304 importCsvClosure: function( grid ) {
19587 return function( importFile ){
22305 return function( importFile ) {
1958822306 var importArray = service.parseCsv( importFile );
19589 if ( !importArray || importArray.length < 1 ){
22307
22308 if ( !importArray || importArray.length < 1 ) {
1959022309 service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
1959122310 return;
1959222311 }
1959322312
1959422313 var newObjects = service.createCsvObjects( grid, importArray );
19595 if ( !newObjects || newObjects.length === 0 ){
22314
22315 if ( !newObjects || newObjects.length === 0 ) {
1959622316 service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
1959722317 return;
1959822318 }
1963622356 * @param {Grid} grid the grid that we want to import into
1963722357 * @param {Array} importArray the data that we want to import, as an array
1963822358 */
19639 createCsvObjects: function( grid, importArray ){
22359 createCsvObjects: function( grid, importArray ) {
1964022360 // pull off header row and turn into headers
1964122361 var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
19642 if ( !headerMapping || headerMapping.length === 0 ){
22362
22363 if ( !headerMapping || headerMapping.length === 0 ) {
1964322364 service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
1964422365 return [];
1964522366 }
1964622367
19647 var newObjects = [];
19648 var newObject;
19649 importArray.forEach( function( row, index ) {
22368 var newObjects = [],
22369 newObject;
22370
22371 importArray.forEach( function( row ) {
1965022372 newObject = service.newObject( grid );
19651 if ( row !== null ){
19652 row.forEach( function( field, index ){
19653 if ( headerMapping[index] !== null ){
22373 if ( row !== null ) {
22374 row.forEach( function( field, index ) {
22375 if ( headerMapping[index] !== null ) {
1965422376 newObject[ headerMapping[index] ] = field;
1965522377 }
1965622378 });
1967822400 */
1967922401 processHeaders: function( grid, headerRow ) {
1968022402 var headers = [];
19681 if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
22403
22404 if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ) {
1968222405 // we are going to create new columnDefs for all these columns, so just remove
1968322406 // spaces from the names to create fields
19684 headerRow.forEach( function( value, index ) {
22407 headerRow.forEach( function( value ) {
1968522408 headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
1968622409 });
1968722410 return headers;
19688 } else {
22411 }
22412 else {
1968922413 var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
19690 headerRow.forEach( function( value, index ) {
22414 headerRow.forEach( function( value ) {
1969122415 if ( lookupHash[value] ) {
1969222416 headers.push( lookupHash[value] );
19693 } else if ( lookupHash[ value.toLowerCase() ] ) {
22417 }
22418 else if ( lookupHash[ value.toLowerCase() ] ) {
1969422419 headers.push( lookupHash[ value.toLowerCase() ] );
19695 } else {
22420 }
22421 else {
1969622422 headers.push( null );
1969722423 }
1969822424 });
1971422440 * @returns {hash} the flattened version of the column def information, allowing
1971522441 * us to look up a value by `flattenedHash[ headerValue ]`
1971622442 */
19717 flattenColumnDefs: function( grid, columnDefs ){
22443 flattenColumnDefs: function( grid, columnDefs ) {
1971822444 var flattenedHash = {};
19719 columnDefs.forEach( function( columnDef, index) {
19720 if ( columnDef.name ){
22445
22446 columnDefs.forEach( function( columnDef) {
22447 if ( columnDef.name ) {
1972122448 flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
1972222449 flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
1972322450 }
1972422451
19725 if ( columnDef.field ){
22452 if ( columnDef.field ) {
1972622453 flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
1972722454 flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
1972822455 }
1972922456
19730 if ( columnDef.displayName ){
22457 if ( columnDef.displayName ) {
1973122458 flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
1973222459 flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
1973322460 }
1973422461
19735 if ( columnDef.displayName && grid.options.importerHeaderFilter ){
22462 if ( columnDef.displayName && grid.options.importerHeaderFilter ) {
1973622463 flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
1973722464 flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
1973822465 }
1976122488 * @param {array} newObjects the objects we want to insert into the grid data
1976222489 * @returns {object} the new object
1976322490 */
19764 addObjects: function( grid, newObjects, $scope ){
19765 if ( grid.api.rowEdit ){
22491 addObjects: function( grid, newObjects ) {
22492 if ( grid.api.rowEdit ) {
1976622493 var dataChangeDereg = grid.registerDataChangeCallback( function() {
1976722494 grid.api.rowEdit.setRowsDirty( newObjects );
1976822495 dataChangeDereg();
1978522512 * @param {Grid} grid the grid we're importing into
1978622513 * @returns {object} the new object
1978722514 */
19788 newObject: function( grid ){
19789 if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
22515 newObject: function( grid ) {
22516 if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ) {
1979022517 return new grid.options.importerNewObject();
19791 } else {
22518 }
22519 else {
1979222520 return {};
1979322521 }
1979422522 },
1980622534 * @param {array} headerRow the header row that we wish to match against
1980722535 * the column definitions
1980822536 */
19809 alertError: function( grid, alertI18nToken, consoleMessage, context ){
19810 if ( grid.options.importerErrorCallback ){
22537 alertError: function( grid, alertI18nToken, consoleMessage, context ) {
22538 if ( grid.options.importerErrorCallback ) {
1981122539 grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
19812 } else {
22540 }
22541 else {
1981322542 $window.alert(i18nService.getSafeText( alertI18nToken ));
1981422543 gridUtil.logError(consoleMessage + context );
1981522544 }
1985922588 return {
1986022589 replace: true,
1986122590 priority: 0,
19862 require: '^uiGrid',
22591 require: '?^uiGrid',
1986322592 scope: false,
1986422593 templateUrl: 'ui-grid/importerMenuItem',
1986522594 link: function ($scope, $elm, $attrs, uiGridCtrl) {
19866 var handleFileSelect = function( event ){
22595 var grid;
22596
22597 function handleFileSelect(event) {
1986722598 var target = event.srcElement || event.target;
1986822599
1986922600 if (target && target.files && target.files.length === 1) {
1987022601 var fileObject = target.files[0];
19871 uiGridImporterService.importThisFile( grid, fileObject );
19872 target.form.reset();
22602
22603 // Define grid if the uiGrid controller is present
22604 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
22605 grid = uiGridCtrl.grid;
22606
22607 uiGridImporterService.importThisFile( grid, fileObject );
22608 target.form.reset();
22609 }
22610 else {
22611 gridUtil.logError('Could not import file because UI Grid was not found.');
22612 }
1987322613 }
19874 };
22614 }
1987522615
1987622616 var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
19877 var grid = uiGridCtrl.grid;
19878
19879 if ( fileChooser.length !== 1 ){
22617
22618 if ( fileChooser.length !== 1 ) {
1988022619 gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
19881 } else {
19882 fileChooser[0].addEventListener('change', handleFileSelect, false); // TODO: why the false on the end? Google
22620 }
22621 else {
22622 fileChooser[0].addEventListener('change', handleFileSelect, false);
1988322623 }
1988422624 }
1988522625 };
1990922649 *
1991022650 * @description Service for infinite scroll features
1991122651 */
19912 module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent, $q) {
19913
22652 module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$rootScope', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $rootScope, uiGridConstants, ScrollEvent, $q) {
1991422653 var service = {
1991522654
1991622655 /**
1992322662 initializeGrid: function(grid, $scope) {
1992422663 service.defaultGridOptions(grid.options);
1992522664
19926 if (!grid.options.enableInfiniteScroll){
22665 if (!grid.options.enableInfiniteScroll) {
1992722666 return;
1992822667 }
1992922668
1999222731 dataLoaded: function( scrollUp, scrollDown ) {
1999322732 service.setScrollDirections(grid, scrollUp, scrollDown);
1999422733
19995 var promise = service.adjustScroll(grid).then(function() {
22734 return service.adjustScroll(grid).then(function() {
1999622735 grid.infiniteScroll.dataLoading = false;
1999722736 });
19998
19999 return promise;
2000022737 },
2000122738
2000222739 /**
2001422751 * infinite scroll events upward
2001522752 * @param {boolean} scrollDown flag that there are pages downwards, so
2001622753 * fire infinite scroll events downward
20017 * @returns {promise} promise that is resolved when the scroll reset is complete
2001822754 */
2001922755 resetScroll: function( scrollUp, scrollDown ) {
2002022756 service.setScrollDirections( grid, scrollUp, scrollDown);
2002122757
20022 return service.adjustInfiniteScrollPosition(grid, 0);
22758 service.adjustInfiniteScrollPosition(grid, 0);
2002322759 },
2002422760
2002522761
2008622822
2008722823
2008822824 defaultGridOptions: function (gridOptions) {
20089 //default option to true unless it was explicitly set to false
22825 // default option to true unless it was explicitly set to false
2009022826 /**
2009122827 * @ngdoc object
2009222828 * @name ui.grid.infiniteScroll.api:GridOptions
2017322909 */
2017422910 handleScroll: function (args) {
2017522911 // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
20176 if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
22912 if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ) {
2017722913 return;
2017822914 }
2017922915
2018022916 if (args.y) {
20181 var percentage;
20182 var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
20183 if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
20184 percentage = args.y.percentage;
20185 if (percentage <= targetPercentage){
20186 service.loadData(args.grid);
22917
22918 // If the user is scrolling very quickly all the way to the top/bottom, the scroll handler can get confused
22919 // about the direction. First we check if they've gone all the way, and data always is loaded in this case.
22920 if (args.y.percentage === 0) {
22921 args.grid.scrollDirection = uiGridConstants.scrollDirection.UP;
22922 service.loadData(args.grid);
22923 }
22924 else if (args.y.percentage === 1) {
22925 args.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN;
22926 service.loadData(args.grid);
22927 }
22928 else { // Scroll position is somewhere in between top/bottom, so determine whether it's far enough to load more data.
22929 var percentage,
22930 targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
22931
22932 if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
22933 percentage = args.y.percentage;
22934 if (percentage <= targetPercentage) {
22935 service.loadData(args.grid);
22936 }
2018722937 }
20188 } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
20189 percentage = 1 - args.y.percentage;
20190 if (percentage <= targetPercentage){
20191 service.loadData(args.grid);
22938 else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
22939 percentage = 1 - args.y.percentage;
22940 if (percentage <= targetPercentage) {
22941 service.loadData(args.grid);
22942 }
2019222943 }
2019322944 }
2019422945 }
2021422965 if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
2021522966 grid.infiniteScroll.dataLoading = true;
2021622967 grid.api.infiniteScroll.raise.needLoadMoreDataTop();
20217 } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
22968 }
22969 else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
2021822970 grid.infiniteScroll.dataLoading = true;
2021922971 grid.api.infiniteScroll.raise.needLoadMoreData();
2022022972 }
2023022982 *
2023122983 * If we're scrolling up we scroll to the first row of the old data set -
2023222984 * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
20233 * the time the data comes back. If we're scrolling down we scoll to the last row of the old data set - so we're
22985 * the time the data comes back. If we're scrolling down we scroll to the last row of the old data set - so we're
2023422986 * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
2023522987 * the data comes back.
2023622988 *
2024222994 * @param {Grid} grid the grid we're working on
2024322995 * @returns {promise} a promise that is resolved when scrolling has finished
2024422996 */
20245 adjustScroll: function(grid){
22997 adjustScroll: function(grid) {
2024622998 var promise = $q.defer();
20247 $timeout(function () {
20248 var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
22999 $rootScope.$applyAsync(function () {
23000 var viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
2024923001
2025023002 viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
2025123003 rowHeight = grid.options.rowHeight;
2025223004
20253 if ( grid.infiniteScroll.direction === undefined ){
23005 if ( grid.infiniteScroll.direction === undefined ) {
2025423006 // called from initialize, tweak our scroll up a little
2025523007 service.adjustInfiniteScrollPosition(grid, 0);
2025623008 }
2026323015 grid.api.infiniteScroll.raise.needLoadMoreData();
2026423016 }
2026523017
20266 if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
23018 if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ) {
2026723019 oldTop = grid.infiniteScroll.prevScrollTop || 0;
2026823020 newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
2026923021 service.adjustInfiniteScrollPosition(grid, newTop);
20270 $timeout( function() {
23022 $rootScope.$applyAsync( function() {
2027123023 promise.resolve();
2027223024 });
2027323025 }
2027423026
20275 if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
23027 if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ) {
2027623028 newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
2027723029 service.adjustInfiniteScrollPosition(grid, newTop);
20278 $timeout( function() {
23030 $rootScope.$applyAsync( function() {
2027923031 promise.resolve();
2028023032 });
2028123033 }
2029223044 * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
2029323045 * @param {Grid} grid the grid we're working on
2029423046 * @param {number} scrollTop the position through the grid that we want to scroll to
20295 * @returns {promise} a promise that is resolved when the scrolling finishes
2029623047 */
2029723048 adjustInfiniteScrollPosition: function (grid, scrollTop) {
2029823049 var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
2030123052 rowHeight = grid.options.rowHeight,
2030223053 scrollHeight = visibleRows*rowHeight-viewportHeight;
2030323054
20304 //for infinite scroll, if there are pages upwards then never allow it to be at the zero position so the up button can be active
23055 // for infinite scroll, if there are pages upwards then never allow it to be at the zero position so the up button can be active
2030523056 if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
2030623057 // using pixels results in a relative scroll, hence we have to use percentage
2030723058 scrollEvent.y = {percentage: 1/scrollHeight};
2032623077 * infinite scroll events upward
2032723078 * @param {boolean} scrollDown flag that there are pages downwards, so
2032823079 * fire infinite scroll events downward
20329 * @returns {promise} a promise that is resolved when the scrolling finishes
2033023080 */
2033123081 dataRemovedTop: function( grid, scrollUp, scrollDown ) {
2033223082 var newVisibleRows, oldTop, newTop, rowHeight;
2034023090 // of rows removed
2034123091 newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
2034223092
20343 return service.adjustInfiniteScrollPosition( grid, newTop );
23093 service.adjustInfiniteScrollPosition( grid, newTop );
2034423094 },
2034523095
2034623096 /**
2035923109 */
2036023110 dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
2036123111 var newTop;
23112
2036223113 service.setScrollDirections( grid, scrollUp, scrollDown );
2036323114
2036423115 newTop = grid.infiniteScroll.prevScrollTop;
2036523116
20366 return service.adjustInfiniteScrollPosition( grid, newTop );
23117 service.adjustInfiniteScrollPosition( grid, newTop );
2036723118 }
2036823119 };
2036923120 return service;
2040723158 priority: -200,
2040823159 scope: false,
2040923160 require: '^uiGrid',
20410 compile: function($scope, $elm, $attr){
23161 compile: function() {
2041123162 return {
2041223163 pre: function($scope, $elm, $attr, uiGridCtrl) {
2041323164 uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
2041823169 }
2041923170 };
2042023171 }]);
20421
2042223172 })();
2042323173
2042423174 (function () {
2044323193 * @name ui.grid.moveColumns.service:uiGridMoveColumnService
2044423194 * @description Service for column moving feature.
2044523195 */
20446 module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $timeout, $log, ScrollEvent, uiGridConstants, gridUtil) {
20447
23196 module.service('uiGridMoveColumnService', ['$q', '$rootScope', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $rootScope, $log, ScrollEvent, uiGridConstants, gridUtil) {
2044823197 var service = {
2044923198 initializeGrid: function (grid) {
2045023199 var self = this;
2046923218 * @eventOf ui.grid.moveColumns.api:PublicApi
2047023219 * @description raised when column is moved
2047123220 * <pre>
20472 * gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
23221 * gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition) {})
2047323222 * </pre>
2047423223 * @param {object} colDef the column that was moved
2047523224 * @param {integer} originalPosition of the column
2056823317 * @methodOf ui.grid.moveColumns
2056923318 * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
2057023319 */
20571 updateColumnCache: function(grid){
23320 updateColumnCache: function(grid) {
2057223321 grid.moveColumns.orderCache = grid.getOnlyDataColumns();
2057323322 },
2057423323 /**
2057823327 * @description dataChangeCallback which uses the cached column order to restore the column order
2057923328 * when it is reset by altering the columnDefs array.
2058023329 */
20581 verifyColumnOrder: function(grid){
23330 verifyColumnOrder: function(grid) {
2058223331 var headerRowOffset = grid.rowHeaderColumns.length;
2058323332 var newIndex;
2058423333
20585 angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
23334 angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex) {
2058623335 newIndex = grid.columns.indexOf(cacheCol);
20587 if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
23336 if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ) {
2058823337 var column = grid.columns.splice(newIndex, 1)[0];
2058923338 grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
2059023339 }
2059123340 });
2059223341 },
2059323342 redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
20594
2059523343 var columns = grid.columns;
23344
23345 if (originalPosition === newPosition) {
23346 return;
23347 }
23348
23349 // check columns in between move-range to make sure they are visible columns
23350 var pos = (originalPosition < newPosition) ? originalPosition + 1 : originalPosition - 1;
23351 var i0 = Math.min(pos, newPosition);
23352 for (i0; i0 <= Math.max(pos, newPosition); i0++) {
23353 if (columns[i0].visible) {
23354 break;
23355 }
23356 }
23357 if (i0 > Math.max(pos, newPosition)) {
23358 // no visible column found, column did not visibly move
23359 return;
23360 }
2059623361
2059723362 var originalColumn = columns[originalPosition];
2059823363 if (originalColumn.colDef.enableColumnMoving) {
2060923374 columns[newPosition] = originalColumn;
2061023375 service.updateColumnCache(grid);
2061123376 grid.queueGridRefresh();
20612 $timeout(function () {
23377 $rootScope.$applyAsync(function () {
2061323378 grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
2061423379 grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
2061523380 });
2072423489 var reducedWidth;
2072523490 var moveOccurred = false;
2072623491
20727 var downFn = function( event ){
20728 //Setting some variables required for calculations.
23492 var downFn = function( event ) {
23493 // Setting some variables required for calculations.
2072923494 gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
20730 if ( $scope.grid.hasLeftContainer() ){
23495 if ( $scope.grid.hasLeftContainer() ) {
2073123496 gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
2073223497 }
2073323498
20734 previousMouseX = event.pageX;
23499 previousMouseX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
2073523500 totalMouseMovement = 0;
2073623501 rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
2073723502
20738 if ( event.type === 'mousedown' ){
23503 if ( event.type === 'mousedown' ) {
2073923504 $document.on('mousemove', moveFn);
2074023505 $document.on('mouseup', upFn);
20741 } else if ( event.type === 'touchstart' ){
23506 }
23507 else if ( event.type === 'touchstart' ) {
2074223508 $document.on('touchmove', moveFn);
2074323509 $document.on('touchend', upFn);
2074423510 }
2074523511 };
2074623512
2074723513 var moveFn = function( event ) {
20748 var changeValue = event.pageX - previousMouseX;
20749 if ( changeValue === 0 ){ return; }
20750 //Disable text selection in Chrome during column move
23514 var pageX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
23515 var changeValue = pageX - previousMouseX;
23516 if ( changeValue === 0 ) { return; }
23517 // Disable text selection in Chrome during column move
2075123518 document.onselectstart = function() { return false; };
2075223519
2075323520 moveOccurred = true;
2075723524 }
2075823525 else if (elmCloned) {
2075923526 moveElement(changeValue);
20760 previousMouseX = event.pageX;
23527 previousMouseX = pageX;
2076123528 }
2076223529 };
2076323530
20764 var upFn = function( event ){
20765 //Re-enable text selection after column move
23531 var upFn = function( event ) {
23532 // Re-enable text selection after column move
2076623533 document.onselectstart = null;
2076723534
20768 //Remove the cloned element on mouse up.
23535 // Remove the cloned element on mouse up.
2076923536 if (movingElm) {
2077023537 movingElm.remove();
2077123538 elmCloned = false;
2077423541 offAllEvents();
2077523542 onDownEvents();
2077623543
20777 if (!moveOccurred){
23544 if (!moveOccurred) {
2077823545 return;
2077923546 }
2078023547
2078923556 }
2079023557 }
2079123558
20792 //Case where column should be moved to a position on its left
23559 var targetIndex;
23560
23561 // Case where column should be moved to a position on its left
2079323562 if (totalMouseMovement < 0) {
2079423563 var totalColumnsLeftWidth = 0;
20795 for (var il = columnIndex - 1; il >= 0; il--) {
20796 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
20797 totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
20798 if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
20799 uiGridMoveColumnService.redrawColumnAtPosition
20800 ($scope.grid, columnIndex, il + 1);
20801 break;
23564 var il;
23565 if ( $scope.grid.isRTL() ) {
23566 for (il = columnIndex + 1; il < columns.length; il++) {
23567 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
23568 totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
23569 if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
23570 uiGridMoveColumnService.redrawColumnAtPosition
23571 ($scope.grid, columnIndex, il - 1);
23572 break;
23573 }
2080223574 }
2080323575 }
2080423576 }
20805 //Case where column should be moved to beginning of the grid.
20806 if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
20807 uiGridMoveColumnService.redrawColumnAtPosition
20808 ($scope.grid, columnIndex, 0);
20809 }
20810 }
20811
20812 //Case where column should be moved to a position on its right
20813 else if (totalMouseMovement > 0) {
20814 var totalColumnsRightWidth = 0;
20815 for (var ir = columnIndex + 1; ir < columns.length; ir++) {
20816 if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
20817 totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
20818 if (totalColumnsRightWidth > totalMouseMovement) {
20819 uiGridMoveColumnService.redrawColumnAtPosition
20820 ($scope.grid, columnIndex, ir - 1);
20821 break;
23577 else {
23578 for (il = columnIndex - 1; il >= 0; il--) {
23579 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
23580 totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
23581 if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
23582 uiGridMoveColumnService.redrawColumnAtPosition
23583 ($scope.grid, columnIndex, il + 1);
23584 break;
23585 }
2082223586 }
2082323587 }
2082423588 }
20825 //Case where column should be moved to end of the grid.
20826 if (totalColumnsRightWidth < totalMouseMovement) {
23589
23590 // Case where column should be moved to beginning (or end in RTL) of the grid.
23591 if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
23592 targetIndex = 0;
23593 if ( $scope.grid.isRTL() ) {
23594 targetIndex = columns.length - 1;
23595 }
2082723596 uiGridMoveColumnService.redrawColumnAtPosition
20828 ($scope.grid, columnIndex, columns.length - 1);
23597 ($scope.grid, columnIndex, targetIndex);
2082923598 }
2083023599 }
23600
23601 // Case where column should be moved to a position on its right
23602 else if (totalMouseMovement > 0) {
23603 var totalColumnsRightWidth = 0;
23604 var ir;
23605 if ( $scope.grid.isRTL() ) {
23606 for (ir = columnIndex - 1; ir > 0; ir--) {
23607 if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
23608 totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
23609 if (totalColumnsRightWidth > totalMouseMovement) {
23610 uiGridMoveColumnService.redrawColumnAtPosition
23611 ($scope.grid, columnIndex, ir);
23612 break;
23613 }
23614 }
23615 }
23616 }
23617 else {
23618 for (ir = columnIndex + 1; ir < columns.length; ir++) {
23619 if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
23620 totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
23621 if (totalColumnsRightWidth > totalMouseMovement) {
23622 uiGridMoveColumnService.redrawColumnAtPosition
23623 ($scope.grid, columnIndex, ir - 1);
23624 break;
23625 }
23626 }
23627 }
23628 }
23629
23630
23631 // Case where column should be moved to end (or beginning in RTL) of the grid.
23632 if (totalColumnsRightWidth < totalMouseMovement) {
23633 targetIndex = columns.length - 1;
23634 if ( $scope.grid.isRTL() ) {
23635 targetIndex = 0;
23636 }
23637 uiGridMoveColumnService.redrawColumnAtPosition
23638 ($scope.grid, columnIndex, targetIndex);
23639 }
23640 }
23641
23642
23643
2083123644 };
2083223645
20833 var onDownEvents = function(){
23646 var onDownEvents = function() {
2083423647 $contentsElm.on('touchstart', downFn);
2083523648 $contentsElm.on('mousedown', downFn);
2083623649 };
2085223665 var cloneElement = function () {
2085323666 elmCloned = true;
2085423667
20855 //Cloning header cell and appending to current header cell.
23668 // Cloning header cell and appending to current header cell.
2085623669 movingElm = $elm.clone();
2085723670 $elm.parent().append(movingElm);
2085823671
20859 //Left of cloned element should be aligned to original header cell.
23672 // Left of cloned element should be aligned to original header cell.
2086023673 movingElm.addClass('movingColumn');
2086123674 var movingElementStyles = {};
20862 var elmLeft;
20863 if (gridUtil.detectBrowser() === 'safari') {
20864 //Correction for Safari getBoundingClientRect,
20865 //which does not correctly compute when there is an horizontal scroll
20866 elmLeft = $elm[0].offsetLeft + $elm[0].offsetWidth - $elm[0].getBoundingClientRect().width;
20867 }
20868 else {
20869 elmLeft = $elm[0].getBoundingClientRect().left;
20870 }
20871 movingElementStyles.left = (elmLeft - gridLeft) + 'px';
23675 movingElementStyles.left = $elm[0].offsetLeft + 'px';
2087223676 var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
2087323677 var elmRight = $elm[0].getBoundingClientRect().right;
2087423678 if (elmRight > gridRight) {
2087923683 };
2088023684
2088123685 var moveElement = function (changeValue) {
20882 //Calculate total column width
23686 // Calculate total column width
2088323687 var columns = $scope.grid.columns;
2088423688 var totalColumnWidth = 0;
2088523689 for (var i = 0; i < columns.length; i++) {
2088823692 }
2088923693 }
2089023694
20891 //Calculate new position of left of column
23695 // Calculate new position of left of column
2089223696 var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
2089323697 var currentElmRight = movingElm[0].getBoundingClientRect().right;
2089423698 var newElementLeft;
2089623700 newElementLeft = currentElmLeft - gridLeft + changeValue;
2089723701 newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
2089823702
20899 //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
23703 // Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
2090023704 if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
20901 movingElm.css({visibility: 'visible', 'left': newElementLeft + 'px'});
23705 movingElm.css({visibility: 'visible', 'left': (movingElm[0].offsetLeft +
23706 (newElementLeft < rightMoveLimit ? changeValue : (rightMoveLimit - currentElmLeft))) + 'px'});
2090223707 }
2090323708 else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
2090423709 changeValue *= 8;
2090723712 scrollEvent.grid.scrollContainers('',scrollEvent);
2090823713 }
2090923714
20910 //Calculate total width of columns on the left of the moving column and the mouse movement
23715 // Calculate total width of columns on the left of the moving column and the mouse movement
2091123716 var totalColumnsLeftWidth = 0;
2091223717 for (var il = 0; il < columns.length; il++) {
2091323718 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
2092623731 totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
2092723732 }
2092823733
20929 //Increase width of moving column, in case the rightmost column was moved and its width was
20930 //decreased because of overflow
23734 // Increase width of moving column, in case the rightmost column was moved and its width was
23735 // decreased because of overflow
2093123736 if (reducedWidth < $scope.col.drawnWidth) {
2093223737 reducedWidth += Math.abs(changeValue);
2093323738 movingElm.css({'width': reducedWidth + 'px'});
2093423739 }
2093523740 };
23741
23742 $scope.$on('$destroy', offAllEvents);
2093623743 }
2093723744 }
2093823745 };
2101023817 },
2101123818 /**
2101223819 * @ngdoc method
23820 * @name getFirstRowIndex
23821 * @methodOf ui.grid.pagination.api:PublicAPI
23822 * @description Returns the index of the first row of the current page.
23823 */
23824 getFirstRowIndex: function () {
23825 if (grid.options.useCustomPagination) {
23826 return grid.options.paginationPageSizes.reduce(function(result, size, index) {
23827 return index < grid.options.paginationCurrentPage - 1 ? result + size : result;
23828 }, 0);
23829 }
23830 return ((grid.options.paginationCurrentPage - 1) * grid.options.paginationPageSize);
23831 },
23832 /**
23833 * @ngdoc method
23834 * @name getLastRowIndex
23835 * @methodOf ui.grid.pagination.api:PublicAPI
23836 * @description Returns the index of the last row of the current page.
23837 */
23838 getLastRowIndex: function () {
23839 if (grid.options.useCustomPagination) {
23840 return publicApi.methods.pagination.getFirstRowIndex() + grid.options.paginationPageSizes[grid.options.paginationCurrentPage - 1] - 1;
23841 }
23842 return Math.min(grid.options.paginationCurrentPage * grid.options.paginationPageSize, grid.options.totalItems) - 1;
23843 },
23844 /**
23845 * @ngdoc method
2101323846 * @name getTotalPages
2101423847 * @methodOf ui.grid.pagination.api:PublicAPI
2101523848 * @description Returns the total number of pages
2101723850 getTotalPages: function () {
2101823851 if (!grid.options.enablePagination) {
2101923852 return null;
23853 }
23854
23855 if (grid.options.useCustomPagination) {
23856 return grid.options.paginationPageSizes.length;
2102023857 }
2102123858
2102223859 return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
2103723874 grid.options.paginationCurrentPage + 1,
2103823875 publicApi.methods.pagination.getTotalPages()
2103923876 );
21040 } else {
23877 }
23878 else {
2104123879 grid.options.paginationCurrentPage++;
2104223880 }
2104323881 },
2107823916 grid.api.registerEventsFromObject(publicApi.events);
2107923917 grid.api.registerMethodsFromObject(publicApi.methods);
2108023918
21081 var processPagination = function( renderableRows ){
23919 var processPagination = function( renderableRows ) {
2108223920 if (grid.options.useExternalPagination || !grid.options.enablePagination) {
2108323921 return renderableRows;
2108423922 }
21085 //client side pagination
23923 // client side pagination
2108623924 var pageSize = parseInt(grid.options.paginationPageSize, 10);
2108723925 var currentPage = parseInt(grid.options.paginationCurrentPage, 10);
2108823926
2108923927 var visibleRows = renderableRows.filter(function (row) { return row.visible; });
2109023928 grid.options.totalItems = visibleRows.length;
2109123929
21092 var firstRow = (currentPage - 1) * pageSize;
23930 var firstRow = publicApi.methods.pagination.getFirstRowIndex();
23931 var lastRow = publicApi.methods.pagination.getLastRowIndex();
23932
2109323933 if (firstRow > visibleRows.length) {
2109423934 currentPage = grid.options.paginationCurrentPage = 1;
2109523935 firstRow = (currentPage - 1) * pageSize;
2109623936 }
21097 return visibleRows.slice(firstRow, firstRow + pageSize);
23937 return visibleRows.slice(firstRow, lastRow + 1);
2109823938 };
2109923939
2110023940 grid.registerRowsProcessor(processPagination, 900 );
2111323953 * @ngdoc property
2111423954 * @name enablePagination
2111523955 * @propertyOf ui.grid.pagination.api:GridOptions
21116 * @description Enables pagination, defaults to true
23956 * @description Enables pagination. Defaults to true.
2111723957 */
2111823958 gridOptions.enablePagination = gridOptions.enablePagination !== false;
2111923959 /**
2112023960 * @ngdoc property
2112123961 * @name enablePaginationControls
2112223962 * @propertyOf ui.grid.pagination.api:GridOptions
21123 * @description Enables the paginator at the bottom of the grid. Turn this off, if you want to implement your
23963 * @description Enables the paginator at the bottom of the grid. Turn this off if you want to implement your
2112423964 * own controls outside the grid.
2112523965 */
2112623966 gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
2112923969 * @name useExternalPagination
2113023970 * @propertyOf ui.grid.pagination.api:GridOptions
2113123971 * @description Disables client side pagination. When true, handle the paginationChanged event and set data
21132 * and totalItems, defaults to `false`
23972 * and totalItems. Defaults to `false`
2113323973 */
2113423974 gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
23975
23976 /**
23977 * @ngdoc property
23978 * @name useCustomPagination
23979 * @propertyOf ui.grid.pagination.api:GridOptions
23980 * @description Disables client-side pagination. When true, handle the `paginationChanged` event and set `data`,
23981 * `firstRowIndex`, `lastRowIndex`, and `totalItems`. Defaults to `false`.
23982 */
23983 gridOptions.useCustomPagination = gridOptions.useCustomPagination === true;
23984
2113523985 /**
2113623986 * @ngdoc property
2113723987 * @name totalItems
2113823988 * @propertyOf ui.grid.pagination.api:GridOptions
21139 * @description Total number of items, set automatically when client side pagination, needs set by user
23989 * @description Total number of items, set automatically when using client side pagination, but needs set by user
2114023990 * for server side pagination
2114123991 */
2114223992 if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
2116024010 if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
2116124011 if (gridOptions.paginationPageSizes.length > 0) {
2116224012 gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
21163 } else {
24013 }
24014 else {
2116424015 gridOptions.paginationPageSize = 0;
2116524016 }
2116624017 }
2119424045 * @param {int} pageSize requested page size
2119524046 */
2119624047 onPaginationChanged: function (grid, currentPage, pageSize) {
21197 grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
21198 if (!grid.options.useExternalPagination) {
21199 grid.queueGridRefresh(); //client side pagination
21200 }
24048 grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
24049 if (!grid.options.useExternalPagination) {
24050 grid.queueGridRefresh(); // client side pagination
24051 }
2120124052 }
2120224053 };
2120324054
2126324114 gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
2126424115 .then(function (contents) {
2126524116 var template = angular.element(contents);
24117
2126624118 $elm.append(template);
2126724119 uiGridCtrl.innerCompile(template);
2126824120 });
2127924131 *
2128024132 * @description Panel for handling pagination
2128124133 */
21282 module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
21283 function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
24134 module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService', 'i18nConstants',
24135 function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService, i18nConstants) {
2128424136 return {
2128524137 priority: -200,
2128624138 scope: true,
2128724139 require: '^uiGrid',
2128824140 link: function ($scope, $elm, $attr, uiGridCtrl) {
2128924141 var defaultFocusElementSelector = '.ui-grid-pager-control-input';
21290 $scope.aria = i18nService.getSafeText('pagination.aria'); //Returns an object with all of the aria labels
21291
21292 $scope.paginationApi = uiGridCtrl.grid.api.pagination;
21293 $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
21294 $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
21295 $scope.paginationOf = i18nService.getSafeText('pagination.of');
21296 $scope.paginationThrough = i18nService.getSafeText('pagination.through');
24142
24143 $scope.aria = i18nService.getSafeText('pagination.aria'); // Returns an object with all of the aria labels
24144
24145 var updateLabels = function() {
24146 $scope.paginationApi = uiGridCtrl.grid.api.pagination;
24147 $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
24148 $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
24149 $scope.paginationOf = i18nService.getSafeText('pagination.of');
24150 $scope.paginationThrough = i18nService.getSafeText('pagination.through');
24151 };
24152
24153 updateLabels();
24154
24155 $scope.$on(i18nConstants.UPDATE_EVENT, updateLabels);
2129724156
2129824157 var options = uiGridCtrl.grid.options;
2129924158
2130024159 uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
21301 adjustment.height = adjustment.height - gridUtil.elementHeight($elm);
24160 if (options.enablePaginationControls) {
24161 adjustment.height = adjustment.height - gridUtil.elementHeight($elm, "padding");
24162 }
2130224163 return adjustment;
2130324164 });
2130424165
2131024171
2131124172 $scope.$on('$destroy', dataChangeDereg);
2131224173
21313 var setShowing = function () {
21314 $scope.showingLow = ((options.paginationCurrentPage - 1) * options.paginationPageSize) + 1;
21315 $scope.showingHigh = Math.min(options.paginationCurrentPage * options.paginationPageSize, options.totalItems);
21316 };
21317
21318 var deregT = $scope.$watch('grid.options.totalItems + grid.options.paginationPageSize', setShowing);
21319
2132024174 var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
21321 if (newValues === oldValues || oldValues === undefined) {
21322 return;
21323 }
21324
21325 if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
21326 options.paginationCurrentPage = 1;
21327 return;
21328 }
21329
21330 if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
21331 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
21332 return;
21333 }
21334
21335 setShowing();
21336 uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
24175 if (newValues === oldValues || oldValues === undefined) {
24176 return;
2133724177 }
21338 );
24178
24179 if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
24180 options.paginationCurrentPage = 1;
24181 return;
24182 }
24183
24184 if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
24185 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
24186 return;
24187 }
24188
24189 uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
24190 });
2133924191
2134024192 $scope.$on('$destroy', function() {
21341 deregT();
2134224193 deregP();
2134324194 });
2134424195
2134524196 $scope.cantPageForward = function () {
21346 if (options.totalItems > 0) {
21347 return options.paginationCurrentPage >= $scope.paginationApi.getTotalPages();
21348 } else {
24197 if ($scope.paginationApi.getTotalPages()) {
24198 return $scope.cantPageToLast();
24199 }
24200 else {
2134924201 return options.data.length < 1;
2135024202 }
2135124203 };
2135224204
2135324205 $scope.cantPageToLast = function () {
21354 if (options.totalItems > 0) {
21355 return $scope.cantPageForward();
21356 } else {
21357 return true;
21358 }
24206 var totalPages = $scope.paginationApi.getTotalPages();
24207
24208 return !totalPages || options.paginationCurrentPage >= totalPages;
2135924209 };
2136024210
2136124211 $scope.cantPageBackward = function () {
2136224212 return options.paginationCurrentPage <= 1;
2136324213 };
2136424214
21365 var focusToInputIf = function(condition){
21366 if (condition){
24215 var focusToInputIf = function(condition) {
24216 if (condition) {
2136724217 gridUtil.focus.bySelector($elm, defaultFocusElementSelector);
2136824218 }
2136924219 };
2137024220
21371 //Takes care of setting focus to the middle element when focus is lost
24221 // Takes care of setting focus to the middle element when focus is lost
2137224222 $scope.pageFirstPageClick = function () {
2137324223 $scope.paginationApi.seek(1);
2137424224 focusToInputIf($scope.cantPageBackward());
2138824238 $scope.paginationApi.seek($scope.paginationApi.getTotalPages());
2138924239 focusToInputIf($scope.cantPageToLast());
2139024240 };
21391
2139224241 }
2139324242 };
2139424243 }
2144224291 pinning: {
2144324292 /**
2144424293 * @ngdoc event
21445 * @name columnPin
24294 * @name columnPinned
2144624295 * @eventOf ui.grid.pinning.api:PublicApi
2144724296 * @description raised when column pin state has changed
2144824297 * <pre>
2148124330 },
2148224331
2148324332 defaultGridOptions: function (gridOptions) {
21484 //default option to true unless it was explicitly set to false
24333 // default option to true unless it was explicitly set to false
2148524334 /**
2148624335 * @ngdoc object
2148724336 * @name ui.grid.pinning.api:GridOptions
2149824347 * <br/>Defaults to true
2149924348 */
2150024349 gridOptions.enablePinning = gridOptions.enablePinning !== false;
21501
24350 /**
24351 * @ngdoc object
24352 * @name hidePinLeft
24353 * @propertyOf ui.grid.pinning.api:GridOptions
24354 * @description Hide Pin Left for the entire grid.
24355 * <br/>Defaults to false
24356 */
24357 gridOptions.hidePinLeft = gridOptions.enablePinning && gridOptions.hidePinLeft;
24358 /**
24359 * @ngdoc object
24360 * @name hidePinRight
24361 * @propertyOf ui.grid.pinning.api:GridOptions
24362 * @description Hide Pin Right pinning for the entire grid.
24363 * <br/>Defaults to false
24364 */
24365 gridOptions.hidePinRight = gridOptions.enablePinning && gridOptions.hidePinRight;
2150224366 },
2150324367
2150424368 pinningColumnBuilder: function (colDef, col, gridOptions) {
21505 //default to true unless gridOptions or colDef is explicitly false
24369 // default to true unless gridOptions or colDef is explicitly false
2150624370
2150724371 /**
2150824372 * @ngdoc object
2152024384 * <br/>Defaults to true
2152124385 */
2152224386 colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
21523
24387 /**
24388 * @ngdoc object
24389 * @name hidePinLeft
24390 * @propertyOf ui.grid.pinning.api:ColumnDef
24391 * @description Hide Pin Left for the individual column.
24392 * <br/>Defaults to false
24393 */
24394 colDef.hidePinLeft = colDef.hidePinLeft === undefined ? gridOptions.hidePinLeft : colDef.hidePinLeft;
24395 /**
24396 * @ngdoc object
24397 * @name hidePinRight
24398 * @propertyOf ui.grid.pinning.api:ColumnDef
24399 * @description Hide Pin Right for the individual column.
24400 * <br/>Defaults to false
24401 */
24402 colDef.hidePinRight = colDef.hidePinRight === undefined ? gridOptions.hidePinRight : colDef.hidePinRight;
2152424403
2152524404 /**
2152624405 * @ngdoc object
2158224461 return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
2158324462 },
2158424463 action: function () {
21585 service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.UNPIN);
24464 service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.NONE);
2158624465 }
2158724466 };
2158824467
21589 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
24468 // Skip from menu if hidePinLeft or hidePinRight is true
24469 if (!colDef.hidePinLeft && !gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
2159024470 col.menuItems.push(pinColumnLeftAction);
2159124471 }
21592 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
24472 if (!colDef.hidePinRight && !gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
2159324473 col.menuItems.push(pinColumnRightAction);
2159424474 }
2159524475 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
2160024480 pinColumn: function(grid, col, container) {
2160124481 if (container === uiGridPinningConstants.container.NONE) {
2160224482 col.renderContainer = null;
24483 col.colDef.pinnedLeft = col.colDef.pinnedRight = false;
2160324484 }
2160424485 else {
2160524486 col.renderContainer = container;
2163724518 }
2163824519 };
2163924520 }]);
21640
21641
2164224521 })();
2164324522
21644 (function(){
24523 (function() {
2164524524 'use strict';
2164624525
2164724526 /**
2165724536 */
2165824537 var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
2165924538
21660 module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
21661 function (gridUtil, $q, $timeout) {
21662
21663 var service = {
21664 defaultGridOptions: function(gridOptions){
21665 //default option to true unless it was explicitly set to false
24539 module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$rootScope',
24540 function (gridUtil, $q, $rootScope) {
24541 return {
24542 defaultGridOptions: function(gridOptions) {
24543 // default option to true unless it was explicitly set to false
2166624544 /**
2166724545 * @ngdoc object
2166824546 * @name ui.grid.resizeColumns.api:GridOptions
2168024558 */
2168124559 gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
2168224560
21683 //legacy support
21684 //use old name if it is explicitly false
21685 if (gridOptions.enableColumnResize === false){
24561 // legacy support
24562 // use old name if it is explicitly false
24563 if (gridOptions.enableColumnResize === false) {
2168624564 gridOptions.enableColumnResizing = false;
2168724565 }
2168824566 },
2168924567
2169024568 colResizerColumnBuilder: function (colDef, col, gridOptions) {
21691
2169224569 var promises = [];
24570
2169324571 /**
2169424572 * @ngdoc object
2169524573 * @name ui.grid.resizeColumns.api:ColumnDef
2170524583 * @description Enable column resizing on an individual column
2170624584 * <br/>Defaults to GridOptions.enableColumnResizing
2170724585 */
21708 //default to true unless gridOptions or colDef is explicitly false
24586 // default to true unless gridOptions or colDef is explicitly false
2170924587 colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
2171024588
2171124589
21712 //legacy support of old option name
21713 if (colDef.enableColumnResize === false){
24590 // legacy support of old option name
24591 if (colDef.enableColumnResize === false) {
2171424592 colDef.enableColumnResizing = false;
2171524593 }
2171624594
2173124609 * @eventOf ui.grid.resizeColumns.api:PublicApi
2173224610 * @description raised when column is resized
2173324611 * <pre>
21734 * gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
24612 * gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange) {})
2173524613 * </pre>
2173624614 * @param {object} colDef the column that was resized
2173724615 * @param {integer} delta of the column size change
2174624624 },
2174724625
2174824626 fireColumnSizeChanged: function (grid, colDef, deltaChange) {
21749 $timeout(function () {
21750 if ( grid.api.colResizable ){
24627 $rootScope.$applyAsync(function () {
24628 if ( grid.api.colResizable ) {
2175124629 grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
2175224630 } else {
2175324631 gridUtil.logError("The resizeable api is not registered, this may indicate that you've included the module but not added the 'ui-grid-resize-columns' directive to your grid definition. Cannot raise any events.");
2175724635
2175824636 // get either this column, or the column next to this column, to resize,
2175924637 // returns the column we're going to resize
21760 findTargetCol: function(col, position, rtlMultiplier){
24638 findTargetCol: function(col, position, rtlMultiplier) {
2176124639 var renderContainer = col.getRenderContainer();
2176224640
2176324641 if (position === 'left') {
2176824646 return col;
2176924647 }
2177024648 }
21771
2177224649 };
21773
21774 return service;
21775
2177624650 }]);
2177724651
2177824652
2183324707 }]);
2183424708
2183524709 // Extend the uiGridHeaderCell directive
21836 module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', '$timeout', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants, $timeout) {
24710 module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants) {
2183724711 return {
2183824712 // Run after the original uiGridHeaderCell
2183924713 priority: -10,
2184824722 var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
2184924723
2185024724 var rtlMultiplier = 1;
21851 //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
24725 // when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
2185224726 if (grid.isRTL()) {
2185324727 $scope.position = 'left';
2185424728 rtlMultiplier = -1;
2185524729 }
2185624730
21857 var displayResizers = function(){
24731 var displayResizers = function() {
2185824732
2185924733 // remove any existing resizers.
2186024734 var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
21861 for ( var i = 0; i < resizers.length; i++ ){
24735 for ( var i = 0; i < resizers.length; i++ ) {
2186224736 angular.element(resizers[i]).remove();
2186324737 }
2186424738
2188724761
2188824762 displayResizers();
2188924763
21890 var waitDisplay = function(){
21891 $timeout(displayResizers);
24764 var waitDisplay = function() {
24765 $scope.$applyAsync(displayResizers);
2189224766 };
2189324767
2189424768 var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
2194524819 module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
2194624820 var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
2194724821
21948 var resizer = {
24822 return {
2194924823 priority: 0,
2195024824 scope: {
2195124825 col: '=',
2195924833 gridLeft = 0,
2196024834 rtlMultiplier = 1;
2196124835
21962 //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
24836 // when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
2196324837 if (uiGridCtrl.grid.isRTL()) {
2196424838 $scope.position = 'left';
2196524839 rtlMultiplier = -1;
2198324857
2198424858 // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
2198524859 // Returns the new recommended with, after constraints applied
21986 function constrainWidth(col, width){
24860 function constrainWidth(col, width) {
2198724861 var newWidth = width;
2198824862
2198924863 // If the new width would be less than the column's allowably minimum width, don't allow it
2204124915 }
2204224916
2204324917
22044 function upFunction(event, args) {
24918 function upFunction(event) {
2204524919 if (event.originalEvent) { event = event.originalEvent; }
2204624920 event.preventDefault();
2204724921
2210724981 // we were touchdown then we listen for touchmove and touchup. Also remove the handler for the equivalent
2210824982 // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
2210924983 // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
22110 if ( event.type === 'touchstart' ){
24984 if ( event.type === 'touchstart' ) {
2211124985 $document.on('touchend', upFunction);
2211224986 $document.on('touchmove', moveFunction);
2211324987 $elm.off('mousedown', downFunction);
22114 } else {
24988 }
24989 else {
2211524990 $document.on('mouseup', upFunction);
2211624991 $document.on('mousemove', moveFunction);
2211724992 $elm.off('touchstart', downFunction);
2213625011
2213725012
2213825013 // On doubleclick, resize to fit all rendered cells
22139 var dblClickFn = function(event, args){
25014 var dblClickFn = function(event, args) {
2214025015 event.stopPropagation();
2214125016
2214225017 var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
2214825023
2214925024 // Go through the rendered rows and find out the max size for the data in this column
2215025025 var maxWidth = 0;
22151 var xDiff = 0;
2215225026
2215325027 // Get the parent render container element
2215425028 var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
2217925053
2218025054 if (width > maxWidth) {
2218125055 maxWidth = width;
22182 xDiff = maxWidth - width;
2218325056 }
2218425057 });
2218525058 });
2218625059
2218725060 // check we're not outside the allowable bounds for this column
22188 col.width = constrainWidth(col, maxWidth);
25061 var newWidth = constrainWidth(col, maxWidth);
25062 var xDiff = newWidth - col.drawnWidth;
25063 col.width = newWidth;
2218925064 col.hasCustomWidth = true;
2219025065
2219125066 refreshCanvas(xDiff);
2219925074 });
2220025075 }
2220125076 };
22202
22203 return resizer;
2220425077 }]);
22205
2220625078 })();
2220725079
2220825080 (function () {
2227725149 * whilst this promise is being resolved.
2227825150 *
2227925151 * <pre>
22280 * gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
25152 * gridApi.rowEdit.on.saveRow(scope,function(rowEntity) {})
2228125153 * </pre>
2228225154 * and somewhere within the event handler:
2228325155 * <pre>
2236325235 * @methodOf ui.grid.rowEdit.api:PublicApi
2236425236 * @name setRowsDirty
2236525237 * @description Sets each of the rows passed in dataRows
22366 * to be dirty. note that if you have only just inserted the
25238 * to be dirty. Note that if you have only just inserted the
2236725239 * rows into your data you will need to wait for a $digest cycle
2236825240 * before the gridRows are present - so often you would wrap this
22369 * call in a $interval or $timeout
25241 * call in a $interval or $timeout. Also, you must pass row.entity
25242 * into this function rather than row objects themselves.
2237025243 * <pre>
2237125244 * $interval( function() {
2237225245 * gridApi.rowEdit.setRowsDirty(myDataRows);
2244825321 return function() {
2244925322 gridRow.isSaving = true;
2245025323
22451 if ( gridRow.rowEditSavePromise ){
25324 if ( gridRow.rowEditSavePromise ) {
2245225325 // don't save the row again if it's already saving - that causes stale object exceptions
2245325326 return gridRow.rowEditSavePromise;
2245425327 }
2245525328
2245625329 var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
2245725330
22458 if ( gridRow.rowEditSavePromise ){
25331 if ( gridRow.rowEditSavePromise ) {
2245925332 gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
2246025333 } else {
2246125334 gridUtil.logError( 'A promise was not returned when saveRow event was raised, either nobody is listening to event, or event handler did not return a promise' );
2253025403
2253125404 gridRow.isError = true;
2253225405
22533 if (!grid.rowEdit.errorRows){
25406 if (!grid.rowEdit.errorRows) {
2253425407 grid.rowEdit.errorRows = [];
2253525408 }
22536 if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
25409 if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ) {
2253725410 grid.rowEdit.errorRows.push( gridRow );
2253825411 }
2253925412 };
2255025423 * @param {array} rowArray the array from which to remove the row
2255125424 * @param {GridRow} gridRow the row that should be removed
2255225425 */
22553 removeRow: function( rowArray, removeGridRow ){
22554 if (typeof(rowArray) === 'undefined' || rowArray === null){
25426 removeRow: function( rowArray, removeGridRow ) {
25427 if (typeof(rowArray) === 'undefined' || rowArray === null) {
2255525428 return;
2255625429 }
2255725430
22558 rowArray.forEach( function( gridRow, index ){
22559 if ( gridRow.uid === removeGridRow.uid ){
25431 rowArray.forEach( function( gridRow, index ) {
25432 if ( gridRow.uid === removeGridRow.uid ) {
2256025433 rowArray.splice( index, 1);
2256125434 }
2256225435 });
2257225445 * @param {array} rowArray the array in which to look for the row
2257325446 * @param {GridRow} gridRow the row that should be looked for
2257425447 */
22575 isRowPresent: function( rowArray, removeGridRow ){
25448 isRowPresent: function( rowArray, removeGridRow ) {
2257625449 var present = false;
22577 rowArray.forEach( function( gridRow, index ){
22578 if ( gridRow.uid === removeGridRow.uid ){
25450 rowArray.forEach( function( gridRow, index ) {
25451 if ( gridRow.uid === removeGridRow.uid ) {
2257925452 present = true;
2258025453 }
2258125454 });
2259825471 * the individual save promises have been resolved.
2259925472 *
2260025473 */
22601 flushDirtyRows: function(grid){
25474 flushDirtyRows: function(grid) {
2260225475 var promises = [];
22603 grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
25476 grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ) {
25477 service.cancelTimer( grid, gridRow );
2260425478 service.saveRow( grid, gridRow )();
2260525479 promises.push( gridRow.rowEditSavePromise );
2260625480 });
2262025494 * @param {object} rowEntity the data entity for which the cell
2262125495 * was edited
2262225496 */
22623 endEditCell: function( rowEntity, colDef, newValue, previousValue ){
25497 endEditCell: function( rowEntity, colDef, newValue, previousValue ) {
2262425498 var grid = this.grid;
2262525499 var gridRow = grid.getRow( rowEntity );
22626 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
22627
22628 if ( newValue !== previousValue || gridRow.isDirty ){
22629 if ( !grid.rowEdit.dirtyRows ){
25500 if ( !gridRow ) { gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
25501
25502 if ( newValue !== previousValue || gridRow.isDirty ) {
25503 if ( !grid.rowEdit.dirtyRows ) {
2263025504 grid.rowEdit.dirtyRows = [];
2263125505 }
2263225506
22633 if ( !gridRow.isDirty ){
25507 if ( !gridRow.isDirty ) {
2263425508 gridRow.isDirty = true;
2263525509 grid.rowEdit.dirtyRows.push( gridRow );
2263625510 }
2265425528 * @param {object} rowEntity the data entity for which the cell
2265525529 * editing has commenced
2265625530 */
22657 beginEditCell: function( rowEntity, colDef ){
25531 beginEditCell: function( rowEntity, colDef ) {
2265825532 var grid = this.grid;
2265925533 var gridRow = grid.getRow( rowEntity );
22660 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
25534 if ( !gridRow ) { gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
2266125535
2266225536 service.cancelTimer( grid, gridRow );
2266325537 },
2267825552 * @param {object} rowEntity the data entity for which the cell
2267925553 * editing was cancelled
2268025554 */
22681 cancelEditCell: function( rowEntity, colDef ){
25555 cancelEditCell: function( rowEntity, colDef ) {
2268225556 var grid = this.grid;
2268325557 var gridRow = grid.getRow( rowEntity );
22684 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
25558 if ( !gridRow ) { gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
2268525559
2268625560 service.considerSetTimer( grid, gridRow );
2268725561 },
2269925573 * @param {object} oldRowCol the row and column that was left
2270025574 *
2270125575 */
22702 navigate: function( newRowCol, oldRowCol ){
25576 navigate: function( newRowCol, oldRowCol ) {
2270325577 var grid = this.grid;
22704 if ( newRowCol.row.rowEditSaveTimer ){
25578 if ( newRowCol.row.rowEditSaveTimer ) {
2270525579 service.cancelTimer( grid, newRowCol.row );
2270625580 }
2270725581
22708 if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
25582 if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ) {
2270925583 service.considerSetTimer( grid, oldRowCol.row );
2271025584 }
2271125585 },
2273825612 * @param {GridRow} gridRow the row for which the timer should be adjusted
2273925613 *
2274025614 */
22741 considerSetTimer: function( grid, gridRow ){
25615 considerSetTimer: function( grid, gridRow ) {
2274225616 service.cancelTimer( grid, gridRow );
2274325617
22744 if ( gridRow.isDirty && !gridRow.isSaving ){
22745 if ( grid.options.rowEditWaitInterval !== -1 ){
25618 if ( gridRow.isDirty && !gridRow.isSaving ) {
25619 if ( grid.options.rowEditWaitInterval !== -1 ) {
2274625620 var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
2274725621 gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
2274825622 }
2276025634 * @param {GridRow} gridRow the row for which the timer should be adjusted
2276125635 *
2276225636 */
22763 cancelTimer: function( grid, gridRow ){
22764 if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
25637 cancelTimer: function( grid, gridRow ) {
25638 if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ) {
2276525639 $interval.cancel(gridRow.rowEditSaveTimer);
2276625640 delete gridRow.rowEditSaveTimer;
2276725641 }
2278925663 */
2279025664 setRowsDirty: function( grid, myDataRows ) {
2279125665 var gridRow;
22792 myDataRows.forEach( function( value, index ){
25666 myDataRows.forEach( function( value, index ) {
2279325667 gridRow = grid.getRow( value );
22794 if ( gridRow ){
22795 if ( !grid.rowEdit.dirtyRows ){
25668 if ( gridRow ) {
25669 if ( !grid.rowEdit.dirtyRows ) {
2279625670 grid.rowEdit.dirtyRows = [];
2279725671 }
2279825672
22799 if ( !gridRow.isDirty ){
25673 if ( !gridRow.isDirty ) {
2280025674 gridRow.isDirty = true;
2280125675 grid.rowEdit.dirtyRows.push( gridRow );
2280225676 }
2282625700 setRowsClean: function( grid, myDataRows ) {
2282725701 var gridRow;
2282825702
22829 myDataRows.forEach( function( value, index ){
25703 myDataRows.forEach( function( value, index ) {
2283025704 gridRow = grid.getRow( value );
22831 if ( gridRow ){
25705 if ( gridRow ) {
2283225706 delete gridRow.isDirty;
2283325707 service.removeRow( grid.rowEdit.dirtyRows, gridRow );
2283425708 service.cancelTimer( grid, gridRow );
2295925833 *
2296025834 * @description Services for saveState feature
2296125835 */
22962 module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
22963 function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
22964
25836 module.service('uiGridSaveStateService',
25837 function () {
2296525838 var service = {
2296625839
2296725840 initializeGrid: function (grid) {
2296825841
22969 //add feature namespace and any properties to grid for needed state
25842 // add feature namespace and any properties to grid for needed state
2297025843 grid.saveState = {};
2297125844 this.defaultGridOptions(grid.options);
2297225845
2300125874 * @description Restores the provided state into the grid
2300225875 * @param {scope} $scope a scope that we can broadcast on
2300325876 * @param {object} state the state that should be restored into the grid
25877 * @returns {object} the promise created by refresh
2300425878 */
2300525879 restore: function ( $scope, state) {
23006 service.restore(grid, $scope, state);
25880 return service.restore(grid, $scope, state);
2300725881 }
2300825882 }
2300925883 }
2301225886 grid.api.registerEventsFromObject(publicApi.events);
2301325887
2301425888 grid.api.registerMethodsFromObject(publicApi.methods);
23015
2301625889 },
2301725890
2301825891 defaultGridOptions: function (gridOptions) {
23019 //default option to true unless it was explicitly set to false
25892 // default option to true unless it was explicitly set to false
2302025893 /**
2302125894 * @ngdoc object
2302225895 * @name ui.grid.saveState.api:GridOptions
2318526058 gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
2318626059 },
2318726060
23188
23189
2319026061 /**
2319126062 * @ngdoc function
2319226063 * @name save
2320426075 savedState.selection = service.saveSelection( grid );
2320526076 savedState.grouping = service.saveGrouping( grid );
2320626077 savedState.treeView = service.saveTreeView( grid );
26078 savedState.pagination = service.savePagination( grid );
2320726079
2320826080 return savedState;
2320926081 },
2321826090 * @param {Grid} grid the grid whose state we'd like to restore
2321926091 * @param {scope} $scope a scope that we can broadcast on
2322026092 * @param {object} state the state we'd like to restore
26093 * @returns {object} the promise created by refresh
2322126094 */
23222 restore: function( grid, $scope, state ){
26095 restore: function( grid, $scope, state ) {
2322326096 if ( state.columns ) {
2322426097 service.restoreColumns( grid, state.columns );
2322526098 }
2322626099
23227 if ( state.scrollFocus ){
26100 if ( state.scrollFocus ) {
2322826101 service.restoreScrollFocus( grid, $scope, state.scrollFocus );
2322926102 }
2323026103
23231 if ( state.selection ){
26104 if ( state.selection ) {
2323226105 service.restoreSelection( grid, state.selection );
2323326106 }
2323426107
23235 if ( state.grouping ){
26108 if ( state.grouping ) {
2323626109 service.restoreGrouping( grid, state.grouping );
2323726110 }
2323826111
23239 if ( state.treeView ){
26112 if ( state.treeView ) {
2324026113 service.restoreTreeView( grid, state.treeView );
2324126114 }
2324226115
23243 grid.refresh();
26116 if ( state.pagination ) {
26117 service.restorePagination( grid, state.pagination );
26118 }
26119
26120 return grid.refresh();
2324426121 },
2324526122
2324626123
2325926136 */
2326026137 saveColumns: function( grid ) {
2326126138 var columns = [];
26139
2326226140 grid.getOnlyDataColumns().forEach( function( column ) {
2326326141 var savedColumn = {};
2326426142 savedColumn.name = column.name;
2326526143
23266 if ( grid.options.saveVisible ){
26144 if ( grid.options.saveVisible ) {
2326726145 savedColumn.visible = column.visible;
2326826146 }
2326926147
23270 if ( grid.options.saveWidths ){
26148 if ( grid.options.saveWidths ) {
2327126149 savedColumn.width = column.width;
2327226150 }
2327326151
2327426152 // these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
23275 if ( grid.options.saveSort ){
26153 if ( grid.options.saveSort ) {
2327626154 savedColumn.sort = angular.copy( column.sort );
2327726155 }
2327826156
23279 if ( grid.options.saveFilter ){
26157 if ( grid.options.saveFilter ) {
2328026158 savedColumn.filters = [];
23281 column.filters.forEach( function( filter ){
26159 column.filters.forEach( function( filter ) {
2328226160 var copiedFilter = {};
2328326161 angular.forEach( filter, function( value, key) {
23284 if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
26162 if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder') {
2328526163 copiedFilter[key] = value;
2328626164 }
2328726165 });
2328926167 });
2329026168 }
2329126169
23292 if ( !!grid.api.pinning && grid.options.savePinning ){
26170 if ( !!grid.api.pinning && grid.options.savePinning ) {
2329326171 savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
2329426172 }
2329526173
2331926197 * @param {Grid} grid the grid whose state we'd like to save
2332026198 * @returns {object} the selection state ready to be saved
2332126199 */
23322 saveScrollFocus: function( grid ){
23323 if ( !grid.api.cellNav ){
26200 saveScrollFocus: function( grid ) {
26201 if ( !grid.api.cellNav ) {
2332426202 return {};
2332526203 }
2332626204
2332726205 var scrollFocus = {};
23328 if ( grid.options.saveFocus ){
26206 if ( grid.options.saveFocus ) {
2332926207 scrollFocus.focus = true;
2333026208 var rowCol = grid.api.cellNav.getFocusedCell();
2333126209 if ( rowCol !== null ) {
23332 if ( rowCol.col !== null ){
26210 if ( rowCol.col !== null ) {
2333326211 scrollFocus.colName = rowCol.col.colDef.name;
2333426212 }
23335 if ( rowCol.row !== null ){
26213 if ( rowCol.row !== null ) {
2333626214 scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
2333726215 }
2333826216 }
2334026218
2334126219 if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
2334226220 scrollFocus.focus = false;
23343 if ( grid.renderContainers.body.prevRowScrollIndex ){
26221 if ( grid.renderContainers.body.prevRowScrollIndex ) {
2334426222 scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
2334526223 }
2334626224
23347 if ( grid.renderContainers.body.prevColScrollIndex ){
26225 if ( grid.renderContainers.body.prevColScrollIndex ) {
2334826226 scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
2334926227 }
2335026228 }
2336126239 * @param {Grid} grid the grid whose state we'd like to save
2336226240 * @returns {array} the selection state ready to be saved
2336326241 */
23364 saveSelection: function( grid ){
23365 if ( !grid.api.selection || !grid.options.saveSelection ){
26242 saveSelection: function( grid ) {
26243 if ( !grid.api.selection || !grid.options.saveSelection ) {
2336626244 return [];
2336726245 }
2336826246
23369 var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
26247 return grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
2337026248 return service.getRowVal( grid, gridRow );
2337126249 });
23372
23373 return selection;
2337426250 },
2337526251
2337626252
2338226258 * @param {Grid} grid the grid whose state we'd like to save
2338326259 * @returns {object} the grouping state ready to be saved
2338426260 */
23385 saveGrouping: function( grid ){
23386 if ( !grid.api.grouping || !grid.options.saveGrouping ){
26261 saveGrouping: function( grid ) {
26262 if ( !grid.api.grouping || !grid.options.saveGrouping ) {
2338726263 return {};
2338826264 }
2338926265
2339026266 return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
26267 },
26268
26269
26270 /**
26271 * @ngdoc function
26272 * @name savePagination
26273 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
26274 * @description Saves the pagination state, if the pagination feature is enabled
26275 * @param {Grid} grid the grid whose state we'd like to save
26276 * @returns {object} the pagination state ready to be saved
26277 */
26278 savePagination: function( grid ) {
26279 if ( !grid.api.pagination || !grid.options.paginationPageSize ) {
26280 return {};
26281 }
26282
26283 return {
26284 paginationCurrentPage: grid.options.paginationCurrentPage,
26285 paginationPageSize: grid.options.paginationPageSize
26286 };
2339126287 },
2339226288
2339326289
2339926295 * @param {Grid} grid the grid whose state we'd like to save
2340026296 * @returns {object} the tree view state ready to be saved
2340126297 */
23402 saveTreeView: function( grid ){
23403 if ( !grid.api.treeView || !grid.options.saveTreeView ){
26298 saveTreeView: function( grid ) {
26299 if ( !grid.api.treeView || !grid.options.saveTreeView ) {
2340426300 return {};
2340526301 }
2340626302
2341926315 * @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
2342026316 *
2342126317 */
23422 getRowVal: function( grid, gridRow ){
26318 getRowVal: function( grid, gridRow ) {
2342326319 if ( !gridRow ) {
2342426320 return null;
2342526321 }
2342626322
2342726323 var rowVal = {};
23428 if ( grid.options.saveRowIdentity ){
26324 if ( grid.options.saveRowIdentity ) {
2342926325 rowVal.identity = true;
2343026326 rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
23431 } else {
26327 }
26328 else {
2343226329 rowVal.identity = false;
2343326330 rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
2343426331 }
2344626343 * @param {Grid} grid the grid whose state we'd like to restore
2344726344 * @param {object} columnsState the list of columns we had before, with their state
2344826345 */
23449 restoreColumns: function( grid, columnsState ){
26346 restoreColumns: function( grid, columnsState ) {
2345026347 var isSortChanged = false;
2345126348
2345226349 columnsState.forEach( function( columnState, index ) {
2345326350 var currentCol = grid.getColumn( columnState.name );
2345426351
23455 if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
26352 if ( currentCol && !grid.isRowHeaderColumn(currentCol) ) {
2345626353 if ( grid.options.saveVisible &&
2345726354 ( currentCol.visible !== columnState.visible ||
23458 currentCol.colDef.visible !== columnState.visible ) ){
26355 currentCol.colDef.visible !== columnState.visible ) ) {
2345926356 currentCol.visible = columnState.visible;
2346026357 currentCol.colDef.visible = columnState.visible;
2346126358 grid.api.core.raise.columnVisibilityChanged(currentCol);
2346226359 }
2346326360
23464 if ( grid.options.saveWidths ){
26361 if ( grid.options.saveWidths && currentCol.width !== columnState.width) {
2346526362 currentCol.width = columnState.width;
26363 currentCol.hasCustomWidth = true;
2346626364 }
2346726365
2346826366 if ( grid.options.saveSort &&
2346926367 !angular.equals(currentCol.sort, columnState.sort) &&
23470 !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
26368 !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ) {
2347126369 currentCol.sort = angular.copy( columnState.sort );
2347226370 isSortChanged = true;
2347326371 }
2347426372
2347526373 if ( grid.options.saveFilter &&
23476 !angular.equals(currentCol.filters, columnState.filters ) ){
23477 columnState.filters.forEach( function( filter, index ){
26374 !angular.equals(currentCol.filters, columnState.filters ) ) {
26375 columnState.filters.forEach( function( filter, index ) {
2347826376 angular.extend( currentCol.filters[index], filter );
23479 if ( typeof(filter.term) === 'undefined' || filter.term === null ){
26377 if ( typeof(filter.term) === 'undefined' || filter.term === null ) {
2348026378 delete currentCol.filters[index].term;
2348126379 }
2348226380 });
2348326381 grid.api.core.raise.filterChanged();
2348426382 }
2348526383
23486 if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
26384 if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ) {
2348726385 grid.api.pinning.pinColumn(currentCol, columnState.pinned);
2348826386 }
2348926387
2351526413 * @param {scope} $scope a scope that we can broadcast on
2351626414 * @param {object} scrollFocusState the scroll/focus state ready to be restored
2351726415 */
23518 restoreScrollFocus: function( grid, $scope, scrollFocusState ){
23519 if ( !grid.api.cellNav ){
26416 restoreScrollFocus: function( grid, $scope, scrollFocusState ) {
26417 if ( !grid.api.cellNav ) {
2352026418 return;
2352126419 }
2352226420
2352326421 var colDef, row;
23524 if ( scrollFocusState.colName ){
26422 if ( scrollFocusState.colName ) {
2352526423 var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
23526 if ( colDefs.length > 0 ){
26424 if ( colDefs.length > 0 ) {
2352726425 colDef = colDefs[0];
2352826426 }
2352926427 }
2353026428
23531 if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
23532 if ( scrollFocusState.rowVal.identity ){
26429 if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ) {
26430 if ( scrollFocusState.rowVal.identity ) {
2353326431 row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
23534 } else {
26432 }
26433 else {
2353526434 row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
2353626435 }
2353726436 }
2353926438 var entity = row && row.entity ? row.entity : null ;
2354026439
2354126440 if ( colDef || entity ) {
23542 if (scrollFocusState.focus ){
26441 if (scrollFocusState.focus ) {
2354326442 grid.api.cellNav.scrollToFocus( entity, colDef );
23544 } else {
26443 }
26444 else {
2354526445 grid.scrollTo( entity, colDef );
2354626446 }
2354726447 }
2355826458 * @param {Grid} grid the grid whose state we'd like to restore
2355926459 * @param {object} selectionState the selection state ready to be restored
2356026460 */
23561 restoreSelection: function( grid, selectionState ){
23562 if ( !grid.api.selection ){
26461 restoreSelection: function( grid, selectionState ) {
26462 if ( !grid.api.selection ) {
2356326463 return;
2356426464 }
2356526465
2356626466 grid.api.selection.clearSelectedRows();
2356726467
23568 selectionState.forEach( function( rowVal ) {
23569 if ( rowVal.identity ){
26468 selectionState.forEach(function( rowVal ) {
26469 if ( rowVal.identity ) {
2357026470 var foundRow = service.findRowByIdentity( grid, rowVal );
2357126471
23572 if ( foundRow ){
26472 if ( foundRow ) {
2357326473 grid.api.selection.selectRow( foundRow.entity );
2357426474 }
2357526475
23576 } else {
26476 }
26477 else {
2357726478 grid.api.selection.selectRowByVisibleIndex( rowVal.row );
2357826479 }
2357926480 });
2358926490 * @param {Grid} grid the grid whose state we'd like to restore
2359026491 * @param {object} groupingState the grouping state ready to be restored
2359126492 */
23592 restoreGrouping: function( grid, groupingState ){
23593 if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
26493 restoreGrouping: function( grid, groupingState ) {
26494 if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ) {
2359426495 return;
2359526496 }
2359626497
2360626507 * @param {Grid} grid the grid whose state we'd like to restore
2360726508 * @param {object} treeViewState the tree view state ready to be restored
2360826509 */
23609 restoreTreeView: function( grid, treeViewState ){
23610 if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
26510 restoreTreeView: function( grid, treeViewState ) {
26511 if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ) {
2361126512 return;
2361226513 }
2361326514
2361426515 grid.api.treeView.setTreeView( treeViewState );
26516 },
26517
26518 /**
26519 * @ngdoc function
26520 * @name restorePagination
26521 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
26522 * @description Restores the pagination information, if pagination is enabled.
26523 * @param {Grid} grid the grid whose state we'd like to restore
26524 * @param {object} pagination the pagination object to be restored
26525 * @param {number} pagination.paginationCurrentPage the page number to restore
26526 * @param {number} pagination.paginationPageSize the number of items displayed per page
26527 */
26528 restorePagination: function( grid, pagination ) {
26529 if ( !grid.api.pagination || !grid.options.paginationPageSize ) {
26530 return;
26531 }
26532
26533 grid.options.paginationCurrentPage = pagination.paginationCurrentPage;
26534 grid.options.paginationPageSize = pagination.paginationPageSize;
2361526535 },
2361626536
2361726537 /**
2362426544 * @param {object} rowVal the row we'd like to find
2362526545 * @returns {gridRow} the found row, or null if none found
2362626546 */
23627 findRowByIdentity: function( grid, rowVal ){
23628 if ( !grid.options.saveRowIdentity ){
26547 findRowByIdentity: function( grid, rowVal ) {
26548 if ( !grid.options.saveRowIdentity ) {
2362926549 return null;
2363026550 }
2363126551
2363226552 var filteredRows = grid.rows.filter( function( gridRow ) {
23633 if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
23634 return true;
23635 } else {
23636 return false;
23637 }
26553 return ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row );
2363826554 });
2363926555
23640 if ( filteredRows.length > 0 ){
26556 if ( filteredRows.length > 0 ) {
2364126557 return filteredRows[0];
2364226558 } else {
2364326559 return null;
2364626562 };
2364726563
2364826564 return service;
23649
2365026565 }
23651 ]);
26566 );
2365226567
2365326568 /**
2365426569 * @ngdoc directive
2372526640 * @description constants available in selection module
2372626641 */
2372726642 module.constant('uiGridSelectionConstants', {
23728 featureName: "selection",
26643 featureName: 'selection',
2372926644 selectionRowHeaderColName: 'selectionRowHeaderCol'
2373026645 });
2373126646
23732 //add methods to GridRow
23733 angular.module('ui.grid').config(['$provide', function($provide) {
23734 $provide.decorator('GridRow', ['$delegate', function($delegate) {
26647 // add methods to GridRow
26648 angular.module('ui.grid').config(['$provide', function ($provide) {
26649 $provide.decorator('GridRow', ['$delegate', function ($delegate) {
2373526650
2373626651 /**
2373726652 * @ngdoc object
2375426669 * @ngdoc object
2375526670 * @name isSelected
2375626671 * @propertyOf ui.grid.selection.api:GridRow
23757 * @description Selected state of row. Should be readonly. Make any changes to selected state using setSelected().
26672 * @description Selected state of row. Should be readonly. Make any changes to selected state using setSelected().
2375826673 * <br/>Defaults to false
2375926674 */
2376026675
23761
23762 /**
23763 * @ngdoc function
23764 * @name setSelected
23765 * @methodOf ui.grid.selection.api:GridRow
23766 * @description Sets the isSelected property and updates the selectedCount
23767 * Changes to isSelected state should only be made via this function
23768 * @param {bool} selected value to set
23769 */
23770 $delegate.prototype.setSelected = function(selected) {
26676 /**
26677 * @ngdoc object
26678 * @name isFocused
26679 * @propertyOf ui.grid.selection.api:GridRow
26680 * @description Focused state of row. Should be readonly. Make any changes to focused state using setFocused().
26681 * <br/>Defaults to false
26682 */
26683
26684 /**
26685 * @ngdoc function
26686 * @name setSelected
26687 * @methodOf ui.grid.selection.api:GridRow
26688 * @description Sets the isSelected property and updates the selectedCount
26689 * Changes to isSelected state should only be made via this function
26690 * @param {Boolean} selected value to set
26691 */
26692 $delegate.prototype.setSelected = function (selected) {
26693 if (selected !== this.isSelected) {
2377126694 this.isSelected = selected;
23772 if (selected) {
23773 this.grid.selection.selectedCount++;
23774 }
23775 else {
23776 this.grid.selection.selectedCount--;
23777 }
23778 };
26695 this.grid.selection.selectedCount += selected ? 1 : -1;
26696 }
26697 };
26698
26699 /**
26700 * @ngdoc function
26701 * @name setFocused
26702 * @methodOf ui.grid.selection.api:GridRow
26703 * @description Sets the isFocused property
26704 * Changes to isFocused state should only be made via this function
26705 * @param {Boolean} val value to set
26706 */
26707 $delegate.prototype.setFocused = function(val) {
26708 if (val !== this.isFocused) {
26709 this.grid.selection.focusedRow && (this.grid.selection.focusedRow.isFocused = false);
26710 this.grid.selection.focusedRow = val ? this : null;
26711 this.isFocused = val;
26712 }
26713 };
2377926714
2378026715 return $delegate;
2378126716 }]);
2378726722 *
2378826723 * @description Services for selection features
2378926724 */
23790 module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
23791 function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
23792
26725 module.service('uiGridSelectionService',
26726 function () {
2379326727 var service = {
2379426728
2379526729 initializeGrid: function (grid) {
2379626730
23797 //add feature namespace and any properties to grid for needed
26731 // add feature namespace and any properties to grid for needed
2379826732 /**
2379926733 * @ngdoc object
2380026734 * @name ui.grid.selection.grid:selection
2380126735 *
2380226736 * @description Grid properties and functions added for selection
2380326737 */
23804 grid.selection = {};
23805 grid.selection.lastSelectedRow = null;
23806 grid.selection.selectAll = false;
26738 grid.selection = {
26739 lastSelectedRow: null,
26740 /**
26741 * @ngdoc object
26742 * @name focusedRow
26743 * @propertyOf ui.grid.selection.grid:selection
26744 * @description Focused row.
26745 */
26746 focusedRow: null,
26747 selectAll: false
26748 };
2380726749
2380826750
2380926751 /**
2382926771 selection: {
2383026772 /**
2383126773 * @ngdoc event
26774 * @name rowFocusChanged
26775 * @eventOf ui.grid.selection.api:PublicApi
26776 * @description is raised after the row.isFocused state is changed
26777 * @param {object} scope the scope associated with the grid
26778 * @param {GridRow} row the row that was focused/unfocused
26779 * @param {Event} evt object if raised from an event
26780 */
26781 rowFocusChanged: function (scope, row, evt) {},
26782 /**
26783 * @ngdoc event
2383226784 * @name rowSelectionChanged
2383326785 * @eventOf ui.grid.selection.api:PublicApi
2383426786 * @description is raised after the row.isSelected state is changed
26787 * @param {object} scope the scope associated with the grid
2383526788 * @param {GridRow} row the row that was selected/deselected
23836 * @param {Event} event object if raised from an event
26789 * @param {Event} evt object if raised from an event
2383726790 */
2383826791 rowSelectionChanged: function (scope, row, evt) {
2383926792 },
2384526798 * in bulk, if the `enableSelectionBatchEvent` option is set to true
2384626799 * (which it is by default). This allows more efficient processing
2384726800 * of bulk events.
26801 * @param {object} scope the scope associated with the grid
2384826802 * @param {array} rows the rows that were selected/deselected
23849 * @param {Event} event object if raised from an event
26803 * @param {Event} evt object if raised from an event
2385026804 */
2385126805 rowSelectionChangedBatch: function (scope, rows, evt) {
2385226806 }
2386026814 * @methodOf ui.grid.selection.api:PublicApi
2386126815 * @description Toggles data row as selected or unselected
2386226816 * @param {object} rowEntity gridOptions.data[] array instance
23863 * @param {Event} event object if raised from an event
26817 * @param {Event} evt object if raised from an event
2386426818 */
2386526819 toggleRowSelection: function (rowEntity, evt) {
2386626820 var row = grid.getRow(rowEntity);
2387426828 * @methodOf ui.grid.selection.api:PublicApi
2387526829 * @description Select the data row
2387626830 * @param {object} rowEntity gridOptions.data[] array instance
23877 * @param {Event} event object if raised from an event
26831 * @param {Event} evt object if raised from an event
2387826832 */
2387926833 selectRow: function (rowEntity, evt) {
2388026834 var row = grid.getRow(rowEntity);
2389026844 * specify row 0 you'll get the first visible row selected). In this context
2389126845 * visible means of those rows that are theoretically visible (i.e. not filtered),
2389226846 * rather than rows currently rendered on the screen.
23893 * @param {number} index index within the rowsVisible array
23894 * @param {Event} event object if raised from an event
26847 * @param {number} rowNum index within the rowsVisible array
26848 * @param {Event} evt object if raised from an event
2389526849 */
23896 selectRowByVisibleIndex: function ( rowNum, evt ) {
26850 selectRowByVisibleIndex: function (rowNum, evt) {
2389726851 var row = grid.renderContainers.body.visibleRowCache[rowNum];
23898 if (row !== null && typeof(row) !== 'undefined' && !row.isSelected) {
26852 if (row !== null && typeof (row) !== 'undefined' && !row.isSelected) {
2389926853 service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
2390026854 }
2390126855 },
2390526859 * @methodOf ui.grid.selection.api:PublicApi
2390626860 * @description UnSelect the data row
2390726861 * @param {object} rowEntity gridOptions.data[] array instance
23908 * @param {Event} event object if raised from an event
26862 * @param {Event} evt object if raised from an event
2390926863 */
2391026864 unSelectRow: function (rowEntity, evt) {
2391126865 var row = grid.getRow(rowEntity);
2391526869 },
2391626870 /**
2391726871 * @ngdoc function
26872 * @name unSelectRowByVisibleIndex
26873 * @methodOf ui.grid.selection.api:PublicApi
26874 * @description Unselect the specified row by visible index (i.e. if you
26875 * specify row 0 you'll get the first visible row unselected). In this context
26876 * visible means of those rows that are theoretically visible (i.e. not filtered),
26877 * rather than rows currently rendered on the screen.
26878 * @param {number} rowNum index within the rowsVisible array
26879 * @param {Event} evt object if raised from an event
26880 */
26881 unSelectRowByVisibleIndex: function (rowNum, evt) {
26882 var row = grid.renderContainers.body.visibleRowCache[rowNum];
26883 if (row !== null && typeof (row) !== 'undefined' && row.isSelected) {
26884 service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
26885 }
26886 },
26887 /**
26888 * @ngdoc function
2391826889 * @name selectAllRows
2391926890 * @methodOf ui.grid.selection.api:PublicApi
2392026891 * @description Selects all rows. Does nothing if multiSelect = false
23921 * @param {Event} event object if raised from an event
26892 * @param {Event} evt object if raised from an event
2392226893 */
2392326894 selectAllRows: function (evt) {
23924 if (grid.options.multiSelect === false) {
23925 return;
26895 if (grid.options.multiSelect !== false) {
26896 var changedRows = [];
26897 grid.rows.forEach(function (row) {
26898 if (!row.isSelected && row.enableSelection !== false && grid.options.isRowSelectable(row) !== false) {
26899 row.setSelected(true);
26900 service.decideRaiseSelectionEvent(grid, row, changedRows, evt);
26901 }
26902 });
26903 grid.selection.selectAll = true;
26904 service.decideRaiseSelectionBatchEvent(grid, changedRows, evt);
2392626905 }
23927
23928 var changedRows = [];
23929 grid.rows.forEach(function (row) {
23930 if ( !row.isSelected && row.enableSelection !== false ){
23931 row.setSelected(true);
23932 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
23933 }
23934 });
23935 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
23936 grid.selection.selectAll = true;
2393726906 },
2393826907 /**
2393926908 * @ngdoc function
2394026909 * @name selectAllVisibleRows
2394126910 * @methodOf ui.grid.selection.api:PublicApi
2394226911 * @description Selects all visible rows. Does nothing if multiSelect = false
23943 * @param {Event} event object if raised from an event
26912 * @param {Event} evt object if raised from an event
2394426913 */
2394526914 selectAllVisibleRows: function (evt) {
23946 if (grid.options.multiSelect === false) {
23947 return;
26915 if (grid.options.multiSelect !== false) {
26916 var changedRows = [];
26917 grid.rows.forEach(function(row) {
26918 if (row.visible) {
26919 if (!row.isSelected && row.enableSelection !== false && grid.options.isRowSelectable(row) !== false) {
26920 row.setSelected(true);
26921 service.decideRaiseSelectionEvent(grid, row, changedRows, evt);
26922 }
26923 } else if (row.isSelected) {
26924 row.setSelected(false);
26925 service.decideRaiseSelectionEvent(grid, row, changedRows, evt);
26926 }
26927 });
26928 grid.selection.selectAll = true;
26929 service.decideRaiseSelectionBatchEvent(grid, changedRows, evt);
2394826930 }
23949
23950 var changedRows = [];
23951 grid.rows.forEach(function (row) {
23952 if (row.visible) {
23953 if (!row.isSelected && row.enableSelection !== false){
23954 row.setSelected(true);
23955 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
23956 }
23957 } else {
23958 if (row.isSelected){
23959 row.setSelected(false);
23960 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
23961 }
23962 }
23963 });
23964 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
23965 grid.selection.selectAll = true;
2396626931 },
2396726932 /**
2396826933 * @ngdoc function
2396926934 * @name clearSelectedRows
2397026935 * @methodOf ui.grid.selection.api:PublicApi
2397126936 * @description Unselects all rows
23972 * @param {Event} event object if raised from an event
26937 * @param {Event} evt object if raised from an event
2397326938 */
2397426939 clearSelectedRows: function (evt) {
2397526940 service.clearSelectedRows(grid, evt);
2398326948 getSelectedRows: function () {
2398426949 return service.getSelectedRows(grid).map(function (gridRow) {
2398526950 return gridRow.entity;
26951 }).filter(function (entity) {
26952 return entity.hasOwnProperty('$$hashKey') || !angular.isObject(entity);
2398626953 });
2398726954 },
2398826955 /**
2399326960 */
2399426961 getSelectedGridRows: function () {
2399526962 return service.getSelectedRows(grid);
26963 },
26964 /**
26965 * @ngdoc function
26966 * @name getSelectedCount
26967 * @methodOf ui.grid.selection.api:PublicApi
26968 * @description returns the number of rows selected
26969 */
26970 getSelectedCount: function () {
26971 return grid.selection.selectedCount;
2399626972 },
2399726973 /**
2399826974 * @ngdoc function
2403827014 },
2403927015
2404027016 defaultGridOptions: function (gridOptions) {
24041 //default option to true unless it was explicitly set to false
27017 // default option to true unless it was explicitly set to false
2404227018 /**
2404327019 * @ngdoc object
2404427020 * @name ui.grid.selection.api:GridOptions
2409527071 * @name enableFullRowSelection
2409627072 * @propertyOf ui.grid.selection.api:GridOptions
2409727073 * @description Enable selection by clicking anywhere on the row. Defaults to
24098 * false if `enableRowHeaderSelection` is true, otherwise defaults to false.
27074 * false if `enableRowHeaderSelection` is true, otherwise defaults to true.
2409927075 */
24100 if ( typeof(gridOptions.enableFullRowSelection) === 'undefined' ){
27076 if (typeof (gridOptions.enableFullRowSelection) === 'undefined') {
2410127077 gridOptions.enableFullRowSelection = !gridOptions.enableRowHeaderSelection;
2410227078 }
27079 /**
27080 * @ngdoc object
27081 * @name enableFocusRowOnRowHeaderClick
27082 * @propertyOf ui.grid.selection.api:GridOptions
27083 * @description Enable focuse row by clicking on the row header. Defaults to
27084 * true if `enableRowHeaderSelection` is true, otherwise defaults to false.
27085 */
27086 gridOptions.enableFocusRowOnRowHeaderClick = (gridOptions.enableFocusRowOnRowHeaderClick !== false)
27087 || !gridOptions.enableRowHeaderSelection;
27088 /**
27089 * @ngdoc object
27090 * @name enableSelectRowOnFocus
27091 * @propertyOf ui.grid.selection.api:GridOptions
27092 * @description Enable focuse row by clicking on the row anywhere. Defaults true.
27093 */
27094 gridOptions.enableSelectRowOnFocus = (gridOptions.enableSelectRowOnFocus !== false);
2410327095 /**
2410427096 * @ngdoc object
2410527097 * @name enableSelectAll
2412727119 * <br/>Defaults to 30px
2412827120 */
2412927121 gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
24130
2413127122 /**
2413227123 * @ngdoc object
2413327124 * @name enableFooterTotalSelected
2414427135 * @propertyOf ui.grid.selection.api:GridOptions
2414527136 * @description Makes it possible to specify a method that evaluates for each row and sets its "enableSelection" property.
2414627137 */
24147
2414827138 gridOptions.isRowSelectable = angular.isDefined(gridOptions.isRowSelectable) ? gridOptions.isRowSelectable : angular.noop;
2414927139 },
2415027140
2415527145 * @description Toggles row as selected or unselected
2415627146 * @param {Grid} grid grid object
2415727147 * @param {GridRow} row row to select or deselect
24158 * @param {Event} event object if resulting from event
27148 * @param {Event} evt object if resulting from event
2415927149 * @param {bool} multiSelect if false, only one row at time can be selected
2416027150 * @param {bool} noUnselect if true then rows cannot be unselected
2416127151 */
2416227152 toggleRowSelection: function (grid, row, evt, multiSelect, noUnselect) {
24163 var selected = row.isSelected;
24164
24165 if ( row.enableSelection === false && !selected ){
27153 if ( row.enableSelection === false ) {
2416627154 return;
2416727155 }
2416827156
24169 var selectedRows;
24170 if (!multiSelect && !selected) {
24171 service.clearSelectedRows(grid, evt);
24172 } else if (!multiSelect && selected) {
24173 selectedRows = service.getSelectedRows(grid);
24174 if (selectedRows.length > 1) {
24175 selected = false; // Enable reselect of the row
27157 var selected = row.isSelected,
27158 selectedRows;
27159
27160 if (!multiSelect) {
27161 if (!selected) {
2417627162 service.clearSelectedRows(grid, evt);
2417727163 }
24178 }
24179
24180 if (selected && noUnselect){
24181 // don't deselect the row
24182 } else {
27164 else {
27165 selectedRows = service.getSelectedRows(grid);
27166 if (selectedRows.length > 1) {
27167 selected = false; // Enable reselect of the row
27168 service.clearSelectedRows(grid, evt);
27169 }
27170 }
27171 }
27172
27173 // only select row in this case
27174 if (!(selected && noUnselect)) {
2418327175 row.setSelected(!selected);
2418427176 if (row.isSelected === true) {
2418527177 grid.selection.lastSelectedRow = row;
2419727189 * @methodOf ui.grid.selection.service:uiGridSelectionService
2419827190 * @description selects a group of rows from the last selected row using the shift key
2419927191 * @param {Grid} grid grid object
24200 * @param {GridRow} clicked row
24201 * @param {Event} event object if raised from an event
27192 * @param {GridRow} row clicked row
27193 * @param {Event} evt object if raised from an event
2420227194 * @param {bool} multiSelect if false, does nothing this is for multiSelect only
2420327195 */
2420427196 shiftSelect: function (grid, row, evt, multiSelect) {
2420827200 var selectedRows = service.getSelectedRows(grid);
2420927201 var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
2421027202 var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
24211 //reverse select direction
27203 // reverse select direction
2421227204 if (fromRow > toRow) {
2421327205 var tmp = fromRow;
2421427206 fromRow = toRow;
2421927211 for (var i = fromRow; i <= toRow; i++) {
2422027212 var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
2422127213 if (rowToSelect) {
24222 if ( !rowToSelect.isSelected && rowToSelect.enableSelection !== false ){
27214 if (!rowToSelect.isSelected && rowToSelect.enableSelection !== false) {
2422327215 rowToSelect.setSelected(true);
2422427216 grid.selection.lastSelectedRow = rowToSelect;
24225 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows, evt );
27217 service.decideRaiseSelectionEvent(grid, rowToSelect, changedRows, evt);
2422627218 }
2422727219 }
2422827220 }
24229 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
27221 service.decideRaiseSelectionBatchEvent(grid, changedRows, evt);
2423027222 },
2423127223 /**
2423227224 * @ngdoc function
2424727239 * @methodOf ui.grid.selection.service:uiGridSelectionService
2424827240 * @description Clears all selected rows
2424927241 * @param {Grid} grid grid object
24250 * @param {Event} event object if raised from an event
27242 * @param {Event} evt object if raised from an event
2425127243 */
2425227244 clearSelectedRows: function (grid, evt) {
2425327245 var changedRows = [];
2425427246 service.getSelectedRows(grid).forEach(function (row) {
24255 if ( row.isSelected ){
27247 if (row.isSelected) {
2425627248 row.setSelected(false);
24257 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
27249 service.decideRaiseSelectionEvent(grid, row, changedRows, evt);
2425827250 }
2425927251 });
24260 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
2426127252 grid.selection.selectAll = false;
2426227253 grid.selection.selectedCount = 0;
27254 service.decideRaiseSelectionBatchEvent(grid, changedRows, evt);
2426327255 },
2426427256
2426527257 /**
2427027262 * @param {Grid} grid grid object
2427127263 * @param {GridRow} row row that has changed
2427227264 * @param {array} changedRows an array to which we can append the changed
24273 * @param {Event} event object if raised from an event
27265 * @param {Event} evt object if raised from an event
2427427266 * row if we're doing batch events
2427527267 */
24276 decideRaiseSelectionEvent: function( grid, row, changedRows, evt ){
24277 if ( !grid.options.enableSelectionBatchEvent ){
27268 decideRaiseSelectionEvent: function (grid, row, changedRows, evt) {
27269 if (!grid.options.enableSelectionBatchEvent) {
2427827270 grid.api.selection.raise.rowSelectionChanged(row, evt);
24279 } else {
27271 }
27272 else {
2428027273 changedRows.push(row);
2428127274 }
2428227275 },
2428927282 * raises it if we do.
2429027283 * @param {Grid} grid grid object
2429127284 * @param {array} changedRows an array of changed rows, only populated
24292 * @param {Event} event object if raised from an event
27285 * @param {Event} evt object if raised from an event
2429327286 * if we're doing batch events
2429427287 */
24295 decideRaiseSelectionBatchEvent: function( grid, changedRows, evt ){
24296 if ( changedRows.length > 0 ){
27288 decideRaiseSelectionBatchEvent: function (grid, changedRows, evt) {
27289 if (changedRows.length > 0) {
2429727290 grid.api.selection.raise.rowSelectionChangedBatch(changedRows, evt);
2429827291 }
2429927292 }
2430027293 };
2430127294
2430227295 return service;
24303
24304 }]);
27296 });
2430527297
2430627298 /**
2430727299 * @ngdoc directive
2433527327 </file>
2433627328 </example>
2433727329 */
24338 module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache', 'uiGridConstants',
24339 function (uiGridSelectionConstants, uiGridSelectionService, $templateCache, uiGridConstants) {
27330 module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', 'uiGridConstants',
27331 function (uiGridSelectionConstants, uiGridSelectionService, uiGridConstants) {
2434027332 return {
2434127333 replace: true,
2434227334 priority: 0,
2435027342 var selectionRowHeaderDef = {
2435127343 name: uiGridSelectionConstants.selectionRowHeaderColName,
2435227344 displayName: '',
24353 width: uiGridCtrl.grid.options.selectionRowHeaderWidth,
27345 width: uiGridCtrl.grid.options.selectionRowHeaderWidth,
2435427346 minWidth: 10,
2435527347 cellTemplate: 'ui-grid/selectionRowHeader',
2435627348 headerCellTemplate: 'ui-grid/selectionHeaderCell',
2436027352 allowCellFocus: true
2436127353 };
2436227354
24363 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
27355 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef, 0);
2436427356 }
2436527357
2436627358 var processorSet = false;
2436727359
24368 var processSelectableRows = function( rows ){
24369 rows.forEach(function(row){
27360 var processSelectableRows = function (rows) {
27361 rows.forEach(function (row) {
2437027362 row.enableSelection = uiGridCtrl.grid.options.isRowSelectable(row);
2437127363 });
2437227364 return rows;
2437327365 };
2437427366
24375 var updateOptions = function(){
27367 var updateOptions = function () {
2437627368 if (uiGridCtrl.grid.options.isRowSelectable !== angular.noop && processorSet !== true) {
2437727369 uiGridCtrl.grid.registerRowsProcessor(processSelectableRows, 500);
2437827370 processorSet = true;
2438127373
2438227374 updateOptions();
2438327375
24384 var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback( updateOptions, [uiGridConstants.dataChange.OPTIONS] );
24385
24386 $scope.$on( '$destroy', dataChangeDereg);
27376 var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(updateOptions, [uiGridConstants.dataChange.OPTIONS]);
27377
27378 $scope.$on('$destroy', dataChangeDereg);
2438727379 },
2438827380 post: function ($scope, $elm, $attrs, uiGridCtrl) {
2438927381
2440127393 template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
2440227394 scope: true,
2440327395 require: '^uiGrid',
24404 link: function($scope, $elm, $attrs, uiGridCtrl) {
27396 link: function ($scope, $elm, $attrs, uiGridCtrl) {
2440527397 var self = uiGridCtrl.grid;
2440627398 $scope.selectButtonClick = selectButtonClick;
27399 $scope.selectButtonKeyDown = selectButtonKeyDown;
2440727400
2440827401 // On IE, prevent mousedowns on the select button from starting a selection.
24409 // If this is not done and you shift+click on another row, the browser will select a big chunk of text
27402 // If this is not done and you shift+click on another row, the browser will select a big chunk of text
2441027403 if (gridUtil.detectBrowser() === 'ie') {
2441127404 $elm.on('mousedown', selectButtonMouseDown);
2441227405 }
2441327406
27407 function selectButtonKeyDown(row, evt) {
27408 if (evt.keyCode === 32) {
27409 evt.preventDefault();
27410 selectButtonClick(row, evt);
27411 }
27412 }
2441427413
2441527414 function selectButtonClick(row, evt) {
2441627415 evt.stopPropagation();
2441927418 uiGridSelectionService.shiftSelect(self, row, evt, self.options.multiSelect);
2442027419 }
2442127420 else if (evt.ctrlKey || evt.metaKey) {
27421 uiGridSelectionService.toggleRowSelection(self, row, evt,
27422 self.options.multiSelect, self.options.noUnselect);
27423 }
27424 else if (row.groupHeader) {
2442227425 uiGridSelectionService.toggleRowSelection(self, row, evt, self.options.multiSelect, self.options.noUnselect);
27426 for (var i = 0; i < row.treeNode.children.length; i++) {
27427 uiGridSelectionService.toggleRowSelection(self, row.treeNode.children[i].row, evt, self.options.multiSelect, self.options.noUnselect);
27428 }
2442327429 }
2442427430 else {
24425 uiGridSelectionService.toggleRowSelection(self, row, evt, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
27431 uiGridSelectionService.toggleRowSelection(self, row, evt,
27432 (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
2442627433 }
27434 self.options.enableFocusRowOnRowHeaderClick && row.setFocused(!row.isFocused) && self.api.selection.raise.rowFocusChanged(row, evt);
2442727435 }
2442827436
2442927437 function selectButtonMouseDown(evt) {
2443227440 window.setTimeout(function () { evt.target.onselectstart = null; }, 0);
2443327441 }
2443427442 }
27443
27444 $scope.$on('$destroy', function unbindEvents() {
27445 $elm.off();
27446 });
2443527447 }
2443627448 };
2443727449 }]);
2444327455 restrict: 'E',
2444427456 template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
2444527457 scope: false,
24446 link: function($scope, $elm, $attrs, uiGridCtrl) {
27458 link: function ($scope) {
2444727459 var self = $scope.col.grid;
2444827460
24449 $scope.headerButtonClick = function(row, evt) {
24450 if ( self.selection.selectAll ){
27461 $scope.headerButtonKeyDown = function (evt) {
27462 if (evt.keyCode === 32 || evt.keyCode === 13) {
27463 evt.preventDefault();
27464 $scope.headerButtonClick(evt);
27465 }
27466 };
27467
27468 $scope.headerButtonClick = function (evt) {
27469 if (self.selection.selectAll) {
2445127470 uiGridSelectionService.clearSelectedRows(self, evt);
24452 if ( self.options.noUnselect ){
27471 if (self.options.noUnselect) {
2445327472 self.api.selection.selectRowByVisibleIndex(0, evt);
2445427473 }
2445527474 self.selection.selectAll = false;
24456 } else {
24457 if ( self.options.multiSelect ){
24458 self.api.selection.selectAllVisibleRows(evt);
24459 self.selection.selectAll = true;
24460 }
27475 }
27476 else if (self.options.multiSelect) {
27477 self.api.selection.selectAllVisibleRows(evt);
27478 self.selection.selectAll = true;
2446127479 }
2446227480 };
2446327481 }
2447327491 * for the grid row
2447427492 */
2447527493 module.directive('uiGridViewport',
24476 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
24477 function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
27494 function () {
2447827495 return {
2447927496 priority: -200, // run after default directive
2448027497 scope: false,
24481 compile: function ($elm, $attrs) {
24482 var rowRepeatDiv = angular.element($elm.children().children()[0]);
24483
24484 var existingNgClass = rowRepeatDiv.attr("ng-class");
24485 var newNgClass = '';
24486 if ( existingNgClass ) {
24487 newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
27498 compile: function ($elm) {
27499 var rowRepeatDiv = angular.element($elm[0].querySelector('.ui-grid-canvas:not(.ui-grid-empty-base-layer-container)').children[0]),
27500 newNgClass = "'ui-grid-row-selected': row.isSelected, 'ui-grid-row-focused': row.isFocused}",
27501 existingNgClass = rowRepeatDiv.attr('ng-class');
27502
27503 if (existingNgClass) {
27504 newNgClass = existingNgClass.slice(0, -1) + ',' + newNgClass;
2448827505 } else {
24489 newNgClass = "{'ui-grid-row-selected': row.isSelected}";
27506 newNgClass = '{' + newNgClass;
2449027507 }
24491 rowRepeatDiv.attr("ng-class", newNgClass);
27508 rowRepeatDiv.attr('ng-class', newNgClass);
2449227509
2449327510 return {
24494 pre: function ($scope, $elm, $attrs, controllers) {
24495
24496 },
24497 post: function ($scope, $elm, $attrs, controllers) {
24498 }
27511 pre: function ($scope, $elm, $attrs, controllers) {},
27512 post: function ($scope, $elm, $attrs, controllers) {}
2449927513 };
2450027514 }
2450127515 };
24502 }]);
27516 });
2450327517
2450427518 /**
2450527519 * @ngdoc directive
2451027524 * @description Stacks on top of ui.grid.uiGridCell to provide selection feature
2451127525 */
2451227526 module.directive('uiGridCell',
24513 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService', '$timeout',
24514 function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService, $timeout) {
27527 ['uiGridConstants', 'uiGridSelectionService',
27528 function (uiGridConstants, uiGridSelectionService) {
2451527529 return {
2451627530 priority: -200, // run after default uiGridCell directive
2451727531 restrict: 'A',
2451827532 require: '?^uiGrid',
2451927533 scope: false,
2452027534 link: function ($scope, $elm, $attrs, uiGridCtrl) {
24521
24522 var touchStartTime = 0;
24523 var touchTimeout = 300;
27535 var touchStartTime = 0,
27536 touchTimeout = 300;
2452427537
2452527538 // Bind to keydown events in the render container
2452627539 if (uiGridCtrl.grid.api.cellNav) {
24527
2452827540 uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
2452927541 if (rowCol === null ||
2453027542 rowCol.row !== $scope.row ||
2453227544 return;
2453327545 }
2453427546
24535 if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
24536 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
27547 if (evt.keyCode === uiGridConstants.keymap.SPACE && $scope.col.colDef.name === 'selectionRowHeaderCol') {
27548 evt.preventDefault();
27549 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt,
27550 ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect),
27551 $scope.grid.options.noUnselect);
2453727552 $scope.$apply();
2453827553 }
24539
24540 // uiGridCellNavService.scrollToIfNecessary(uiGridCtrl.grid, rowCol.row, rowCol.col);
2454127554 });
2454227555 }
2454327556
24544 //$elm.bind('keydown', function (evt) {
24545 // if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
24546 // uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
24547 // $scope.$apply();
24548 // }
24549 //});
24550
24551 var selectCells = function(evt){
27557 var selectCells = function (evt) {
27558 // if you click on expandable icon doesn't trigger selection
27559 if (evt.target.className === "ui-grid-icon-minus-squared" || evt.target.className === "ui-grid-icon-plus-squared") {
27560 return;
27561 }
27562
2455227563 // if we get a click, then stop listening for touchend
2455327564 $elm.off('touchend', touchEnd);
2455427565
2455627567 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect);
2455727568 }
2455827569 else if (evt.ctrlKey || evt.metaKey) {
24559 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
27570 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt,
27571 $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
2456027572 }
24561 else {
24562 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
27573 else if ($scope.grid.options.enableSelectRowOnFocus) {
27574 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt,
27575 ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect),
27576 $scope.grid.options.noUnselect);
2456327577 }
27578 $scope.row.setFocused(!$scope.row.isFocused);
27579 $scope.grid.api.selection.raise.rowFocusChanged($scope.row, evt);
2456427580 $scope.$apply();
2456527581
2456627582 // don't re-enable the touchend handler for a little while - some devices generate both, and it will
2456727583 // take a little while to move your hand from the mouse to the screen if you have both modes of input
24568 $timeout(function() {
27584 window.setTimeout(function () {
2456927585 $elm.on('touchend', touchEnd);
2457027586 }, touchTimeout);
2457127587 };
2457227588
24573 var touchStart = function(evt){
27589 var touchStart = function () {
2457427590 touchStartTime = (new Date()).getTime();
2457527591
2457627592 // if we get a touch event, then stop listening for click
2457727593 $elm.off('click', selectCells);
2457827594 };
2457927595
24580 var touchEnd = function(evt) {
27596 var touchEnd = function (evt) {
2458127597 var touchEndTime = (new Date()).getTime();
2458227598 var touchTime = touchEndTime - touchStartTime;
2458327599
24584 if (touchTime < touchTimeout ) {
27600 if (touchTime < touchTimeout) {
2458527601 // short touch
2458627602 selectCells(evt);
2458727603 }
2458827604
2458927605 // don't re-enable the click handler for a little while - some devices generate both, and it will
2459027606 // take a little while to move your hand from the screen to the mouse if you have both modes of input
24591 $timeout(function() {
27607 window.setTimeout(function () {
2459227608 $elm.on('click', selectCells);
2459327609 }, touchTimeout);
2459427610 };
2459527611
2459627612 function registerRowSelectionEvents() {
24597 if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection) {
27613 if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection && $scope.col.colDef.name !== 'selectionRowHeaderCol') {
2459827614 $elm.addClass('ui-grid-disable-selection');
2459927615 $elm.on('touchstart', touchStart);
2460027616 $elm.on('touchend', touchEnd);
2460427620 }
2460527621 }
2460627622
24607 function deregisterRowSelectionEvents() {
24608 if ($scope.registered){
27623 function unregisterRowSelectionEvents() {
27624 if ($scope.registered) {
2460927625 $elm.removeClass('ui-grid-disable-selection');
24610
2461127626 $elm.off('touchstart', touchStart);
2461227627 $elm.off('touchend', touchEnd);
2461327628 $elm.off('click', selectCells);
2461727632 }
2461827633
2461927634 registerRowSelectionEvents();
27635
2462027636 // register a dataChange callback so that we can change the selection configuration dynamically
2462127637 // if the user changes the options
24622 var dataChangeDereg = $scope.grid.registerDataChangeCallback( function() {
24623 if ( $scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
24624 !$scope.registered ){
27638 var dataChangeUnreg = $scope.grid.registerDataChangeCallback(function () {
27639 if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
27640 !$scope.registered) {
2462527641 registerRowSelectionEvents();
24626 } else if ( ( !$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection ) &&
24627 $scope.registered ){
24628 deregisterRowSelectionEvents();
2462927642 }
24630 }, [uiGridConstants.dataChange.OPTIONS] );
24631
24632 $elm.on( '$destroy', dataChangeDereg);
27643 else if ((!$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection) &&
27644 $scope.registered) {
27645 unregisterRowSelectionEvents();
27646 }
27647 }, [uiGridConstants.dataChange.OPTIONS]);
27648
27649 $elm.on('$destroy', dataChangeUnreg);
2463327650 }
2463427651 };
2463527652 }]);
2463627653
24637 module.directive('uiGridGridFooter', ['$compile', 'uiGridConstants', 'gridUtil', function ($compile, uiGridConstants, gridUtil) {
27654 module.directive('uiGridGridFooter', ['$compile', 'gridUtil', function ($compile, gridUtil) {
2463827655 return {
2463927656 restrict: 'EA',
2464027657 replace: true,
2464127658 priority: -1000,
2464227659 require: '^uiGrid',
2464327660 scope: true,
24644 compile: function ($elm, $attrs) {
27661 compile: function () {
2464527662 return {
2464627663 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
24647
2464827664 if (!uiGridCtrl.grid.options.showGridFooter) {
2464927665 return;
2465027666 }
2465127667
24652
2465327668 gridUtil.getTemplate('ui-grid/gridFooterSelectedItems')
2465427669 .then(function (contents) {
2465527670 var template = angular.element(contents);
2465927674 angular.element($elm[0].getElementsByClassName('ui-grid-grid-footer')[0]).append(newElm);
2466027675 });
2466127676 },
24662
2466327677 post: function ($scope, $elm, $attrs, controllers) {
2466427678
2466527679 }
2466727681 }
2466827682 };
2466927683 }]);
24670
2467127684 })();
2467227685
2467327686 (function () {
2480027813
2480127814 var service = {
2480227815
24803 initializeGrid: function (grid, $scope) {
24804
24805 //add feature namespace and any properties to grid for needed
27816 initializeGrid: function (grid) {
27817
27818 // add feature namespace and any properties to grid for needed
2480627819 /**
2480727820 * @ngdoc object
2480827821 * @name ui.grid.treeBase.grid:treeBase
2490927922 * all transient information on the tree (children, childCount) and recalculate it
2491027923 *
2491127924 */
24912 grid.treeBase.tree = {};
27925 grid.treeBase.tree = [];
2491327926
2491427927 service.defaultGridOptions(grid.options);
2491527928
2493927952 * When the data is loaded the grid will automatically refresh to show these new rows
2494027953 *
2494127954 * <pre>
24942 * gridApi.treeBase.on.rowExpanded(scope,function(row){})
27955 * gridApi.treeBase.on.rowExpanded(scope,function(row) {})
2494327956 * </pre>
2494427957 * @param {gridRow} row the row that was expanded. You can also
2494527958 * retrieve the grid from this row with row.grid
2495427967 * a purpose at the moment, included for symmetry
2495527968 *
2495627969 * <pre>
24957 * gridApi.treeBase.on.rowCollapsed(scope,function(row){})
27970 * gridApi.treeBase.on.rowCollapsed(scope,function(row) {})
2495827971 * </pre>
2495927972 * @param {gridRow} row the row that was collapsed. You can also
2496027973 * retrieve the grid from this row with row.grid
2500228015 * @methodOf ui.grid.treeBase.api:PublicApi
2500328016 * @description expand the immediate children of the specified row
2500428017 * @param {gridRow} row the row you wish to expand
28018 * @param {boolean} recursive true if you wish to expand the row's ancients
2500528019 */
25006 expandRow: function (row) {
25007 service.expandRow(grid, row);
28020 expandRow: function (row, recursive) {
28021 service.expandRow(grid, row, recursive);
2500828022 },
2500928023
2501028024 /**
2508428098 * @returns {Array} array of children of this row, the children
2508528099 * are all gridRows
2508628100 */
25087 getRowChildren: function ( row ){
25088 return row.treeNode.children.map( function( childNode ){
28101 getRowChildren: function ( row ) {
28102 return row.treeNode.children.map( function( childNode ) {
2508928103 return childNode.row;
2509028104 });
2509128105 }
2510028114
2510128115
2510228116 defaultGridOptions: function (gridOptions) {
25103 //default option to true unless it was explicitly set to false
28117 // default option to true unless it was explicitly set to false
2510428118 /**
2510528119 * @ngdoc object
2510628120 * @name ui.grid.treeBase.api:GridOptions
2512728141 * but will make the tree row header wider
2512828142 * <br/>Defaults to 10
2512928143 */
25130 gridOptions.treeIndent = gridOptions.treeIndent || 10;
28144 gridOptions.treeIndent = (gridOptions.treeIndent != null) ? gridOptions.treeIndent : 10;
2513128145
2513228146 /**
2513328147 * @ngdoc object
2513428148 * @name showTreeRowHeader
2513528149 * @propertyOf ui.grid.treeBase.api:GridOptions
25136 * @description If set to false, don't create the row header. Youll need to programatically control the expand
28150 * @description If set to false, don't create the row header. You'll need to programmatically control the expand
2513728151 * states
2513828152 * <br/>Defaults to true
2513928153 */
2517328187 * {
2517428188 * aggregationName: {
2517528189 * label: (optional) string,
25176 * aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
25177 * finalizerFn: (optional) function( aggregation ){...}
25178 * },
28190 * aggregationFn: function( aggregation, fieldValue, numValue, row ) {...},
28191 * finalizerFn: (optional) function( aggregation ) {...}
28192 * },
2517928193 * mean: {
2518028194 * label: 'mean',
25181 * aggregationFn: function( aggregation, fieldValue, numValue ){
25182 * aggregation.count = (aggregation.count || 1) + 1;
28195 * aggregationFn: function( aggregation, fieldValue, numValue ) {
28196 * aggregation.count = (aggregation.count || 1) + 1;
2518328197 * aggregation.sum = (aggregation.sum || 0) + numValue;
2518428198 * },
25185 * finalizerFn: function( aggregation ){
28199 * finalizerFn: function( aggregation ) {
2518628200 * aggregation.value = aggregation.sum / aggregation.count
2518728201 * }
2518828202 * }
2519728211 * <br/>Defaults to {}
2519828212 */
2519928213 gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
28214
28215 /**
28216 * @ngdoc object
28217 * @name enableExpandAll
28218 * @propertyOf ui.grid.treeBase.api:GridOptions
28219 * @description Enable the expand all button at the top of the row header
28220 *
28221 * <br/>Defaults to true
28222 */
28223 gridOptions.enableExpandAll = gridOptions.enableExpandAll !== false;
2520028224 },
2520128225
2520228226
2520728231 * @description Sets the tree defaults based on the columnDefs
2520828232 *
2520928233 * @param {object} colDef columnDef we're basing on
25210 * @param {GridCol} col the column we're to update
28234 * @param {GridColumn} col the column we're to update
2521128235 * @param {object} gridOptions the options we should use
2521228236 * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
2521328237 */
2522728251 * a number (most aggregations work on numbers)
2522828252 * @example
2522928253 * <pre>
25230 * customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
28254 * customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ) {
2523128255 * // calculates the average of the squares of the values
25232 * if ( typeof(aggregation.count) === 'undefined' ){
28256 * if ( typeof(aggregation.count) === 'undefined' ) {
2523328257 * aggregation.count = 0;
2523428258 * }
2523528259 * aggregation.count++;
2523628260 *
25237 * if ( !isNaN(numValue) ){
25238 * if ( typeof(aggregation.total) === 'undefined' ){
28261 * if ( !isNaN(numValue) ) {
28262 * if ( typeof(aggregation.total) === 'undefined' ) {
2523928263 * aggregation.total = 0;
2524028264 * }
2524128265 * aggregation.total = aggregation.total + numValue * numValue;
2524628270 * </pre>
2524728271 * <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
2524828272 */
25249 if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
28273 if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ) {
2525028274 col.treeAggregationFn = colDef.customTreeAggregationFn;
2525128275 }
2525228276
2527328297 * <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
2527428298 * <br/>Defaults to undefined.
2527528299 */
25276 if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
28300 if ( typeof(colDef.treeAggregationType) !== 'undefined' ) {
2527728301 col.treeAggregation = { type: colDef.treeAggregationType };
25278 if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
28302 if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ) {
2527928303 col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
2528028304 col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
2528128305 col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
25282 } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
28306 }
28307 else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ) {
2528328308 col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
2528428309 col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
2528528310 }
2529128316 * @propertyOf ui.grid.treeBase.api:ColumnDef
2529228317 * @description A custom label to use for this aggregation. If provided we don't use native i18n.
2529328318 */
25294 if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
25295 if (typeof(col.treeAggregation) === 'undefined' ){
28319 if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ) {
28320 if (typeof(col.treeAggregation) === 'undefined' ) {
2529628321 col.treeAggregation = {};
2529728322 }
2529828323 col.treeAggregation.label = colDef.treeAggregationLabel;
2534028365 *
2534128366 * @example
2534228367 * <pre>
25343 * customTreeAggregationFinalizerFn = function ( aggregation ){
28368 * customTreeAggregationFinalizerFn = function ( aggregation ) {
2534428369 * aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
2534528370 * }
2534628371 * </pre>
2534728372 * <br/>Defaults to undefined.
2534828373 */
25349 if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
28374 if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ) {
2535028375 col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
2535128376 }
2535228377
2536228387 *
2536328388 * @param {Grid} grid grid object
2536428389 */
25365 createRowHeader: function( grid ){
28390 createRowHeader: function( grid ) {
2536628391 var rowHeaderColumnDef = {
2536728392 name: uiGridTreeBaseConstants.rowHeaderColName,
2536828393 displayName: '',
2537728402 };
2537828403
2537928404 rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
25380 grid.addRowHeaderColumn( rowHeaderColumnDef );
28405 grid.addRowHeaderColumn(rowHeaderColumnDef, -100);
2538128406 },
2538228407
2538328408
2542928454 * @param {string} targetState the state we want to set it to
2543028455 */
2543128456 setAllNodes: function (grid, treeNode, targetState) {
25432 if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
28457 if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ) {
2543328458 treeNode.state = targetState;
2543428459
25435 if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
28460 if ( targetState === uiGridTreeBaseConstants.EXPANDED ) {
2543628461 grid.api.treeBase.raise.rowExpanded(treeNode.row);
25437 } else {
28462 }
28463 else {
2543828464 grid.api.treeBase.raise.rowCollapsed(treeNode.row);
2543928465 }
2544028466 }
2544128467
2544228468 // set all child nodes
25443 if ( treeNode.children ){
25444 treeNode.children.forEach(function( childNode ){
28469 if ( treeNode.children ) {
28470 treeNode.children.forEach(function( childNode ) {
2544528471 service.setAllNodes(grid, childNode, targetState);
2544628472 });
2544728473 }
2545828484 * @param {Grid} grid grid object
2545928485 * @param {GridRow} row the row we want to toggle
2546028486 */
25461 toggleRowTreeState: function ( grid, row ){
25462 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
28487 toggleRowTreeState: function ( grid, row ) {
28488 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) {
2546328489 return;
2546428490 }
2546528491
25466 if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
28492 if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED) {
2546728493 service.collapseRow(grid, row);
25468 } else {
25469 service.expandRow(grid, row);
28494 }
28495 else {
28496 service.expandRow(grid, row, false);
2547028497 }
2547128498
2547228499 grid.queueGridRefresh();
2548128508 *
2548228509 * @param {Grid} grid grid object
2548328510 * @param {GridRow} row the row we want to expand
28511 * @param {boolean} recursive true if you wish to expand the row's ancients
2548428512 */
25485 expandRow: function ( grid, row ){
25486 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
25487 return;
25488 }
25489
25490 if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
25491 row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
25492 grid.api.treeBase.raise.rowExpanded(row);
25493 grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
25494 grid.queueGridRefresh();
28513 expandRow: function ( grid, row, recursive ) {
28514 if ( recursive ) {
28515 var parents = [];
28516 while ( row && typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 && row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ) {
28517 parents.push(row);
28518 row = row.treeNode.parentRow;
28519 }
28520
28521 if ( parents.length > 0 ) {
28522 row = parents.pop();
28523 while ( row ) {
28524 row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
28525 grid.api.treeBase.raise.rowExpanded(row);
28526 row = parents.pop();
28527 }
28528
28529 grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
28530 grid.queueGridRefresh();
28531 }
28532 }
28533 else {
28534 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) {
28535 return;
28536 }
28537
28538 if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ) {
28539 row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
28540 grid.api.treeBase.raise.rowExpanded(row);
28541 grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
28542 grid.queueGridRefresh();
28543 }
2549528544 }
2549628545 },
2549728546
2550528554 * @param {Grid} grid grid object
2550628555 * @param {GridRow} row the row we want to expand
2550728556 */
25508 expandRowChildren: function ( grid, row ){
25509 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
28557 expandRowChildren: function ( grid, row ) {
28558 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) {
2551028559 return;
2551128560 }
2551228561
2552528574 * @param {Grid} grid grid object
2552628575 * @param {GridRow} row the row we want to collapse
2552728576 */
25528 collapseRow: function( grid, row ){
25529 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
28577 collapseRow: function( grid, row ) {
28578 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) {
2553028579 return;
2553128580 }
2553228581
25533 if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
28582 if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ) {
2553428583 row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
2553528584 grid.treeBase.expandAll = false;
2553628585 grid.api.treeBase.raise.rowCollapsed(row);
2554828597 * @param {Grid} grid grid object
2554928598 * @param {GridRow} row the row we want to collapse
2555028599 */
25551 collapseRowChildren: function( grid, row ){
25552 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
28600 collapseRowChildren: function( grid, row ) {
28601 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) {
2555328602 return;
2555428603 }
2555528604
2557428623 * @param {object} tree the grid to check
2557528624 * @returns {boolean} whether or not the tree is all expanded
2557628625 */
25577 allExpanded: function( tree ){
28626 allExpanded: function( tree ) {
2557828627 var allExpanded = true;
25579 tree.forEach( function( node ){
25580 if ( !service.allExpandedInternal( node ) ){
28628
28629 tree.forEach(function( node ) {
28630 if ( !service.allExpandedInternal( node ) ) {
2558128631 allExpanded = false;
2558228632 }
2558328633 });
2558428634 return allExpanded;
2558528635 },
2558628636
25587 allExpandedInternal: function( treeNode ){
25588 if ( treeNode.children && treeNode.children.length > 0 ){
25589 if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
28637 allExpandedInternal: function( treeNode ) {
28638 if ( treeNode.children && treeNode.children.length > 0 ) {
28639 if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ) {
2559028640 return false;
2559128641 }
2559228642 var allExpanded = true;
25593 treeNode.children.forEach( function( node ){
25594 if ( !service.allExpandedInternal( node ) ){
28643 treeNode.children.forEach( function( node ) {
28644 if ( !service.allExpandedInternal( node ) ) {
2559528645 allExpanded = false;
2559628646 }
2559728647 });
2559828648 return allExpanded;
25599 } else {
28649 }
28650 else {
2560028651 return true;
2560128652 }
2560228653 },
2562428675 * @returns {array} the updated rows
2562528676 */
2562628677 treeRows: function( renderableRows ) {
25627 if (renderableRows.length === 0){
28678 var grid = this;
28679
28680 if (renderableRows.length === 0) {
28681 service.updateRowHeaderWidth( grid );
2562828682 return renderableRows;
2562928683 }
25630
25631 var grid = this;
25632 var currentLevel = 0;
25633 var currentState = uiGridTreeBaseConstants.EXPANDED;
25634 var parents = [];
2563528684
2563628685 grid.treeBase.tree = service.createTree( grid, renderableRows );
2563728686 service.updateRowHeaderWidth( grid );
2565928708 *
2566028709 * @param {Grid} grid the grid we want to set the row header on
2566128710 */
25662 updateRowHeaderWidth: function( grid ){
25663 var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
25664
25665 var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
25666 if ( rowHeader && newWidth !== rowHeader.width ){
28711 updateRowHeaderWidth: function( grid ) {
28712 var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName),
28713 newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
28714
28715 if ( rowHeader && newWidth !== rowHeader.width ) {
2566728716 rowHeader.width = newWidth;
2566828717 grid.queueRefresh();
2566928718 }
2567028719
2567128720 var newVisibility = true;
25672 if ( grid.options.showTreeRowHeader === false ){
28721
28722 if ( grid.options.showTreeRowHeader === false ) {
2567328723 newVisibility = false;
2567428724 }
25675 if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
28725 if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ) {
2567628726 newVisibility = false;
2567728727 }
25678 if ( rowHeader.visible !== newVisibility ) {
28728 if ( rowHeader && rowHeader.visible !== newVisibility ) {
2567928729 rowHeader.visible = newVisibility;
2568028730 rowHeader.colDef.visible = newVisibility;
2568128731 grid.queueGridRefresh();
2569028740 * @description Creates an array of rows based on the tree, exporting only
2569128741 * the visible nodes and leaves
2569228742 *
25693 * @param {array} nodeList the list of nodes - can be grid.treeBase.tree, or can be node.children when
28743 * @param {array} nodeList The list of nodes - can be grid.treeBase.tree, or can be node.children when
2569428744 * we're calling recursively
2569528745 * @returns {array} renderable rows
2569628746 */
25697 renderTree: function( nodeList ){
28747 renderTree: function( nodeList ) {
2569828748 var renderableRows = [];
2569928749
25700 nodeList.forEach( function ( node ){
25701 if ( node.row.visible ){
28750 nodeList.forEach( function ( node ) {
28751 if ( node.row.visible ) {
2570228752 renderableRows.push( node.row );
2570328753 }
25704 if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
28754 if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ) {
2570528755 renderableRows = renderableRows.concat( service.renderTree( node.children ) );
2570628756 }
2570728757 });
2571528765 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
2571628766 * @description Creates a tree from the renderableRows
2571728767 *
25718 * @param {Grid} grid the grid
25719 * @param {array} renderableRows the rows we want to create a tree from
25720 * @returns {object} the tree we've build
28768 * @param {Grid} grid The grid
28769 * @param {array} renderableRows The rows we want to create a tree from
28770 * @returns {object} The tree we've build
2572128771 */
2572228772 createTree: function( grid, renderableRows ) {
25723 var currentLevel = -1;
25724 var parents = [];
25725 var currentState;
28773 var currentLevel = -1,
28774 parents = [],
28775 currentState;
28776
2572628777 grid.treeBase.tree = [];
2572728778 grid.treeBase.numberLevels = 0;
28779
2572828780 var aggregations = service.getAggregations( grid );
2572928781
25730 var createNode = function( row ){
25731 if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
28782 function createNode( row ) {
28783 if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ) {
2573228784 row.treeLevel = row.entity.$$treeLevel;
2573328785 }
2573428786
25735 if ( row.treeLevel <= currentLevel ){
28787 if ( row.treeLevel <= currentLevel ) {
2573628788 // pop any levels that aren't parents of this level, formatting the aggregation at the same time
25737 while ( row.treeLevel <= currentLevel ){
28789 while ( row.treeLevel <= currentLevel ) {
2573828790 var lastParent = parents.pop();
2573928791 service.finaliseAggregations( lastParent );
2574028792 currentLevel--;
2574128793 }
2574228794
2574328795 // reset our current state based on the new parent, set to expanded if this is a level 0 node
25744 if ( parents.length > 0 ){
28796 if ( parents.length > 0 ) {
2574528797 currentState = service.setCurrentState(parents);
25746 } else {
28798 }
28799 else {
2574728800 currentState = uiGridTreeBaseConstants.EXPANDED;
2574828801 }
2574928802 }
2575028803
2575128804 // aggregate if this is a leaf node
25752 if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible ){
28805 if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible ) {
2575328806 service.aggregate( grid, row, parents );
2575428807 }
2575528808
2575628809 // add this node to the tree
2575728810 service.addOrUseNode(grid, row, parents, aggregations);
2575828811
25759 if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
28812 if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ) {
2576028813 parents.push(row);
2576128814 currentLevel++;
2576228815 currentState = service.setCurrentState(parents);
2576328816 }
2576428817
2576528818 // update the tree number of levels, so we can set header width if we need to
25766 if ( grid.treeBase.numberLevels < row.treeLevel + 1){
28819 if ( grid.treeBase.numberLevels < row.treeLevel + 1) {
2576728820 grid.treeBase.numberLevels = row.treeLevel + 1;
2576828821 }
25769 };
28822 }
2577028823
2577128824 renderableRows.forEach( createNode );
2577228825
25773 // finalise remaining aggregations
25774 while ( parents.length > 0 ){
28826 // finalize remaining aggregations
28827 while ( parents.length > 0 ) {
2577528828 var lastParent = parents.pop();
2577628829 service.finaliseAggregations( lastParent );
2577728830 }
2578728840 * @description Creates a tree node for this row. If this row already has a treeNode
2578828841 * recorded against it, preserves the state, but otherwise overwrites the data.
2578928842 *
25790 * @param {grid} grid the grid we're operating on
25791 * @param {gridRow} row the row we want to set
25792 * @param {array} parents an array of the parents this row should have
25793 * @param {array} aggregationBase empty aggregation information
25794 * @returns {undefined} updates the parents array, updates the row to have a treeNode, and updates the
28843 * @param {grid} grid The grid we're operating on
28844 * @param {gridRow} row The row we want to set
28845 * @param {array} parents An array of the parents this row should have
28846 * @param {array} aggregationBase Empty aggregation information
28847 * @returns {undefined} Updates the parents array, updates the row to have a treeNode, and updates the
2579528848 * grid.treeBase.tree
2579628849 */
25797 addOrUseNode: function( grid, row, parents, aggregationBase ){
28850 addOrUseNode: function( grid, row, parents, aggregationBase ) {
2579828851 var newAggregations = [];
25799 aggregationBase.forEach( function(aggregation){
28852 aggregationBase.forEach( function(aggregation) {
2580028853 newAggregations.push(service.buildAggregationObject(aggregation.col));
2580128854 });
2580228855
2580328856 var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
25804 if ( row.treeNode ){
28857 if ( row.treeNode ) {
2580528858 newNode.state = row.treeNode.state;
2580628859 }
25807 if ( parents.length > 0 ){
28860 if ( parents.length > 0 ) {
2580828861 newNode.parentRow = parents[parents.length - 1];
2580928862 }
2581028863 row.treeNode = newNode;
2581128864
25812 if ( parents.length === 0 ){
28865 if ( parents.length === 0 ) {
2581328866 grid.treeBase.tree.push( newNode );
2581428867 } else {
2581528868 parents[parents.length - 1].treeNode.children.push( newNode );
2582528878 * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
2582628879 * expanded.
2582728880 *
25828 * @param {array} parents an array of the parents this row should have
25829 * @returns {string} the state we should be setting to any nodes we see
28881 * @param {array} parents An array of the parents this row should have
28882 * @returns {string} The state we should be setting to any nodes we see
2583028883 */
25831 setCurrentState: function( parents ){
28884 setCurrentState: function( parents ) {
2583228885 var currentState = uiGridTreeBaseConstants.EXPANDED;
25833 parents.forEach( function(parent){
25834 if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
28886
28887 parents.forEach( function(parent) {
28888 if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ) {
2583528889 currentState = uiGridTreeBaseConstants.COLLAPSED;
2583628890 }
2583728891 });
2585328907 * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
2585428908 * nodes
2585528909 *
25856 * @param {Grid} grid the grid to get the aggregation information from
25857 * @returns {array} the aggregation information
28910 * @param {Grid} grid The grid to get the aggregation information from
28911 * @returns {array} The aggregation information
2585828912 */
25859 sortTree: function( grid ){
28913 sortTree: function( grid ) {
2586028914 grid.columns.forEach( function( column ) {
25861 if ( column.sort && column.sort.ignoreSort ){
28915 if ( column.sort && column.sort.ignoreSort ) {
2586228916 delete column.sort.ignoreSort;
2586328917 }
2586428918 });
2586628920 grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
2586728921 },
2586828922
25869 sortInternal: function( grid, treeList ){
25870 var rows = treeList.map( function( node ){
28923 sortInternal: function( grid, treeList ) {
28924 var rows = treeList.map( function( node ) {
2587128925 return node.row;
2587228926 });
2587328927
2587428928 rows = rowSorter.sort( grid, rows, grid.columns );
2587528929
25876 var treeNodes = rows.map( function( row ){
28930 var treeNodes = rows.map( function( row ) {
2587728931 return row.treeNode;
2587828932 });
2587928933
25880 treeNodes.forEach( function( node ){
25881 if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
28934 treeNodes.forEach( function( node ) {
28935 if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ) {
2588228936 node.children = service.sortInternal( grid, node.children );
2588328937 }
2588428938 });
2590128955 *
2590228956 * @param {Grid} grid the grid to fix filters on
2590328957 */
25904 fixFilter: function( grid ){
28958 fixFilter: function( grid ) {
2590528959 var parentsVisible;
2590628960
25907 grid.treeBase.tree.forEach( function( node ){
25908 if ( node.children && node.children.length > 0 ){
28961 grid.treeBase.tree.forEach( function( node ) {
28962 if ( node.children && node.children.length > 0 ) {
2590928963 parentsVisible = node.row.visible;
2591028964 service.fixFilterInternal( node.children, parentsVisible );
2591128965 }
2591328967 },
2591428968
2591528969 fixFilterInternal: function( nodes, parentsVisible) {
25916 nodes.forEach( function( node ){
25917 if ( node.row.visible && !parentsVisible ){
28970 nodes.forEach(function( node ) {
28971 if ( node.row.visible && !parentsVisible ) {
2591828972 service.setParentsVisible( node );
2591928973 parentsVisible = true;
2592028974 }
2592128975
25922 if ( node.children && node.children.length > 0 ){
28976 if ( node.children && node.children.length > 0 ) {
2592328977 if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
2592428978 parentsVisible = true;
2592528979 }
2592928983 return parentsVisible;
2593028984 },
2593128985
25932 setParentsVisible: function( node ){
25933 while ( node.parentRow ){
28986 setParentsVisible: function( node ) {
28987 while ( node.parentRow ) {
2593428988 node.parentRow.visible = true;
2593528989 node = node.parentRow.treeNode;
2593628990 }
2594328997 * @description Build the object which is stored on the column for holding meta-data about the aggregation.
2594428998 * This method should only be called with columns which have an aggregation.
2594528999 *
25946 * @param {Column} the column which this object relates to
25947 * @returns {object} {col: Column object, label: string, type: string (optional)}
29000 * @param {GridColumn} column The column which this object relates to
29001 * @returns {object} {col: GridColumn object, label: string, type: string (optional)}
2594829002 */
25949 buildAggregationObject: function( column ){
29003 buildAggregationObject: function( column ) {
2595029004 var newAggregation = { col: column };
2595129005
25952 if ( column.treeAggregation && column.treeAggregation.type ){
29006 if ( column.treeAggregation && column.treeAggregation.type ) {
2595329007 newAggregation.type = column.treeAggregation.type;
2595429008 }
2595529009
25956 if ( column.treeAggregation && column.treeAggregation.label ){
29010 if ( column.treeAggregation && column.treeAggregation.label ) {
2595729011 newAggregation.label = column.treeAggregation.label;
2595829012 }
2595929013
2597029024 * @param {Grid} grid the grid to get the aggregation information from
2597129025 * @returns {array} the aggregation information
2597229026 */
25973 getAggregations: function( grid ){
29027 getAggregations: function( grid ) {
2597429028 var aggregateArray = [];
2597529029
25976 grid.columns.forEach( function(column){
25977 if ( typeof(column.treeAggregationFn) !== 'undefined' ){
29030 grid.columns.forEach( function(column) {
29031 if ( typeof(column.treeAggregationFn) !== 'undefined' ) {
2597829032 aggregateArray.push( service.buildAggregationObject(column) );
2597929033
25980 if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
29034 if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ) {
2598129035 // Add aggregation object for footer
2598229036 column.treeFooterAggregation = service.buildAggregationObject(column);
2598329037 column.aggregationType = service.treeFooterAggregationType;
2600129055 * @param {GridRow} row the row we want to set grouping visibility on
2600229056 * @param {array} parents the parents that we would want to aggregate onto
2600329057 */
26004 aggregate: function( grid, row, parents ){
26005 if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
26006 row.treeNode.aggregations.forEach(function(aggregation){
29058 aggregate: function( grid, row, parents ) {
29059 if (parents.length === 0 && row.treeNode && row.treeNode.aggregations) {
29060 row.treeNode.aggregations.forEach(function(aggregation) {
2600729061 // Calculate aggregations for footer even if there are no grouped rows
26008 if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
29062 if (typeof(aggregation.col.treeFooterAggregation) !== 'undefined') {
2600929063 var fieldValue = grid.getCellValue(row, aggregation.col);
2601029064 var numValue = Number(fieldValue);
26011 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
29065 if (aggregation.col.treeAggregationFn) {
29066 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
29067 } else {
29068 aggregation.col.treeFooterAggregation.value = undefined;
29069 }
2601229070 }
2601329071 });
2601429072 }
2601529073
26016 parents.forEach( function( parent, index ){
26017 if ( parent.treeNode.aggregations ){
26018 parent.treeNode.aggregations.forEach( function( aggregation ){
29074 parents.forEach( function( parent, index ) {
29075 if (parent.treeNode.aggregations) {
29076 parent.treeNode.aggregations.forEach( function( aggregation ) {
2601929077 var fieldValue = grid.getCellValue(row, aggregation.col);
2602029078 var numValue = Number(fieldValue);
2602129079 aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
2602229080
26023 if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
26024 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
29081 if (index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined') {
29082 if (aggregation.col.treeAggregationFn) {
29083 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
29084 } else {
29085 aggregation.col.treeFooterAggregation.value = undefined;
29086 }
2602529087 }
2602629088 });
2602729089 }
2603129093
2603229094 // Aggregation routines - no doco needed as self evident
2603329095 nativeAggregations: function() {
26034 var nativeAggregations = {
29096 return {
2603529097 count: {
2603629098 label: i18nService.get().aggregation.count,
2603729099 menuTitle: i18nService.get().grouping.aggregate_count,
2607529137 max: {
2607629138 label: i18nService.get().aggregation.max,
2607729139 menuTitle: i18nService.get().grouping.aggregate_max,
26078 aggregationFn: function( aggregation, fieldValue, numValue ){
26079 if ( typeof(aggregation.value) === 'undefined' ){
29140 aggregationFn: function( aggregation, fieldValue, numValue ) {
29141 if ( typeof(aggregation.value) === 'undefined' ) {
2608029142 aggregation.value = fieldValue;
2608129143 } else {
26082 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
29144 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)) {
2608329145 aggregation.value = fieldValue;
2608429146 }
2608529147 }
2608929151 avg: {
2609029152 label: i18nService.get().aggregation.avg,
2609129153 menuTitle: i18nService.get().grouping.aggregate_avg,
26092 aggregationFn: function( aggregation, fieldValue, numValue ){
26093 if ( typeof(aggregation.count) === 'undefined' ){
29154 aggregationFn: function( aggregation, fieldValue, numValue ) {
29155 if ( typeof(aggregation.count) === 'undefined' ) {
2609429156 aggregation.count = 1;
2609529157 } else {
2609629158 aggregation.count++;
2609729159 }
2609829160
26099 if ( isNaN(numValue) ){
29161 if ( isNaN(numValue) ) {
2610029162 return;
2610129163 }
2610229164
26103 if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
29165 if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ) {
2610429166 aggregation.value = numValue;
2610529167 aggregation.sum = numValue;
2610629168 } else {
2611029172 }
2611129173 }
2611229174 };
26113 return nativeAggregations;
2611429175 },
2611529176
2611629177 /**
2611929180 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
2612029181 * @description Helper function used to finalize aggregation nodes and footer cells
2612129182 *
26122 * @param {gridRow} row the parent we're finalising
26123 * @param {aggregation} the aggregation object manipulated by the aggregationFn
29183 * @param {gridRow} row The parent we're finalising
29184 * @param {aggregation} aggregation The aggregation object manipulated by the aggregationFn
2612429185 */
26125 finaliseAggregation: function(row, aggregation){
26126 if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
29186 finaliseAggregation: function(row, aggregation) {
29187 if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ) {
2612729188 angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
2612829189 }
2612929190
26130 if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
29191 if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ) {
2613129192 aggregation.col.treeAggregationFinalizerFn( aggregation );
2613229193 }
26133 if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
29194 if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ) {
2613429195 aggregation.col.customTreeAggregationFinalizerFn( aggregation );
2613529196 }
26136 if ( typeof(aggregation.rendered) === 'undefined' ){
29197 if ( typeof(aggregation.rendered) === 'undefined' ) {
2613729198 aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
2613829199 }
2613929200 },
2615629217 *
2615729218 * @param {gridRow} row the parent we're finalising
2615829219 */
26159 finaliseAggregations: function( row ){
26160 if ( typeof(row.treeNode.aggregations) === 'undefined' ){
29220 finaliseAggregations: function( row ) {
29221 if ( row == null || typeof(row.treeNode.aggregations) === 'undefined' ) {
2616129222 return;
2616229223 }
2616329224
2616429225 row.treeNode.aggregations.forEach( function( aggregation ) {
2616529226 service.finaliseAggregation(row, aggregation);
2616629227
26167 if ( aggregation.col.treeAggregationUpdateEntity ){
29228 if ( aggregation.col.treeAggregationUpdateEntity ) {
2616829229 var aggregationCopy = {};
26169 angular.forEach( aggregation, function( value, key ){
26170 if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
29230
29231 angular.forEach( aggregation, function( value, key ) {
29232 if ( aggregation.hasOwnProperty(key) && key !== 'col' ) {
2617129233 aggregationCopy[key] = value;
2617229234 }
2617329235 });
2618429246 * @description Uses the tree aggregation functions and finalizers to set the
2618529247 * column footer aggregations.
2618629248 *
26187 * @param {rows} visible rows. not used, but accepted to match signature of GridColumn.aggregationType
26188 * @param {gridColumn} the column we are finalizing
29249 * @param {rows} rows The visible rows. not used, but accepted to match signature of GridColumn.aggregationType
29250 * @param {GridColumn} column The column we are finalizing
2618929251 */
2619029252 treeFooterAggregationType: function( rows, column ) {
2619129253 service.finaliseAggregation(undefined, column.treeFooterAggregation);
26192 if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
29254 if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ) {
2619329255 // The was apparently no aggregation performed (perhaps this is a grouped column
2619429256 return '';
2619529257 }
2619829260 };
2619929261
2620029262 return service;
26201
2620229263 }]);
2620329264
2620429265
2621929280 require: '^uiGrid',
2622029281 link: function($scope, $elm, $attrs, uiGridCtrl) {
2622129282 var self = uiGridCtrl.grid;
29283 $scope.treeButtonClass = function(row) {
29284 if ( ( self.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) {
29285 if (row.treeNode.state === 'expanded' ) {
29286 return 'ui-grid-icon-minus-squared';
29287 }
29288 if (row.treeNode.state === 'collapsed' ) {
29289 return 'ui-grid-icon-plus-squared';
29290 }
29291 }
29292 };
2622229293 $scope.treeButtonClick = function(row, evt) {
29294 evt.stopPropagation();
2622329295 uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
2622429296 };
2622529297 }
2624129313 restrict: 'E',
2624229314 template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
2624329315 scope: false,
26244 link: function($scope, $elm, $attrs, uiGridCtrl) {
29316 link: function($scope) {
2624529317 var self = $scope.col.grid;
26246
29318 $scope.headerButtonClass = function() {
29319 if (self.treeBase.numberLevels > 0 && self.treeBase.expandAll) {
29320 return 'ui-grid-icon-minus-squared';
29321 }
29322 if (self.treeBase.numberLevels > 0 && !self.treeBase.expandAll) {
29323 return 'ui-grid-icon-plus-squared';
29324 }
29325 };
2624729326 $scope.headerButtonClick = function(row, evt) {
26248 if ( self.treeBase.expandAll ){
29327 if ( self.treeBase.expandAll ) {
2624929328 uiGridTreeBaseService.collapseAllRows(self, evt);
2625029329 } else {
2625129330 uiGridTreeBaseService.expandAllRows(self, evt);
2626429343 * @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
2626529344 */
2626629345 module.directive('uiGridViewport',
26267 ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
26268 function ($compile, uiGridConstants, gridUtil, $parse) {
29346 function () {
2626929347 return {
2627029348 priority: -200, // run after default directive
2627129349 scope: false,
26272 compile: function ($elm, $attrs) {
29350 compile: function ($elm) {
2627329351 var rowRepeatDiv = angular.element($elm.children().children()[0]);
2627429352
2627529353 var existingNgClass = rowRepeatDiv.attr("ng-class");
2629029368 };
2629129369 }
2629229370 };
26293 }]);
29371 });
2629429372 })();
2629529373
2629629374 (function () {
2639429472 },
2639529473
2639629474 defaultGridOptions: function (gridOptions) {
26397 //default option to true unless it was explicitly set to false
29475 // default option to true unless it was explicitly set to false
2639829476 /**
2639929477 * @ngdoc object
2640029478 * @name ui.grid.treeView.api:GridOptions
2643629514 adjustSorting: function( renderableRows ) {
2643729515 var grid = this;
2643829516
26439 grid.columns.forEach( function( column ){
26440 if ( column.sort ){
29517 grid.columns.forEach( function( column ) {
29518 if ( column.sort ) {
2644129519 column.sort.ignoreSort = true;
2644229520 }
2644329521 });
2644429522
2644529523 return renderableRows;
2644629524 }
26447
2644829525 };
2644929526
2645029527 return service;
26451
2645229528 }]);
2645329529
2645429530 /**
2649529571 compile: function () {
2649629572 return {
2649729573 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
26498 if (uiGridCtrl.grid.options.enableTreeView !== false){
29574 if (uiGridCtrl.grid.options.enableTreeView !== false) {
2649929575 uiGridTreeViewService.initializeGrid(uiGridCtrl.grid, $scope);
2650029576 }
2650129577 },
2650829584 }]);
2650929585 })();
2651029586
29587 (function () {
29588 'use strict';
29589
29590 /**
29591 * @ngdoc overview
29592 * @name ui.grid.validate
29593 * @description
29594 *
29595 * # ui.grid.validate
29596 *
29597 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
29598 *
29599 * This module provides the ability to validate cells upon change.
29600 *
29601 * Design information:
29602 * -------------------
29603 *
29604 * Validation is not based on angularjs validation, since it would work only when editing the field.
29605 *
29606 * Instead it adds custom properties to any field considered as invalid.
29607 *
29608 * <br/>
29609 * <br/>
29610 *
29611 * <div doc-module-components="ui.grid.expandable"></div>
29612 */
29613 var module = angular.module('ui.grid.validate', ['ui.grid']);
29614
29615 /**
29616 * @ngdoc service
29617 * @name ui.grid.validate.service:uiGridValidateService
29618 *
29619 * @description Services for validation features
29620 */
29621 module.service('uiGridValidateService', ['$sce', '$q', '$http', 'i18nService', 'uiGridConstants', function ($sce, $q, $http, i18nService, uiGridConstants) {
29622
29623 var service = {
29624
29625 /**
29626 * @ngdoc object
29627 * @name validatorFactories
29628 * @propertyOf ui.grid.validate.service:uiGridValidateService
29629 * @description object containing all the factories used to validate data.<br/>
29630 * These factories will be in the form <br/>
29631 * ```
29632 * {
29633 * validatorFactory: function(argument) {
29634 * return function(newValue, oldValue, rowEntity, colDef) {
29635 * return true || false || promise
29636 * }
29637 * },
29638 * messageFunction: function(argument) {
29639 * return string
29640 * }
29641 * }
29642 * ```
29643 *
29644 * Promises should return true or false as result according to the result of validation.
29645 */
29646 validatorFactories: {},
29647
29648 /**
29649 * @ngdoc service
29650 * @name setExternalFactoryFunction
29651 * @methodOf ui.grid.validate.service:uiGridValidateService
29652 * @description Adds a way to retrieve validators from an external service
29653 * <p>Validators from this external service have a higher priority than default
29654 * ones
29655 * @param {function} externalFactoryFunction a function that accepts name and argument to pass to a
29656 * validator factory and that returns an object with the same properties as
29657 * you can see in {@link ui.grid.validate.service:uiGridValidateService#properties_validatorFactories validatorFactories}
29658 */
29659 setExternalFactoryFunction: function(externalFactoryFunction) {
29660 service.externalFactoryFunction = externalFactoryFunction;
29661 },
29662
29663 /**
29664 * @ngdoc service
29665 * @name clearExternalFactory
29666 * @methodOf ui.grid.validate.service:uiGridValidateService
29667 * @description Removes any link to external factory from this service
29668 */
29669 clearExternalFactory: function() {
29670 delete service.externalFactoryFunction;
29671 },
29672
29673 /**
29674 * @ngdoc service
29675 * @name getValidatorFromExternalFactory
29676 * @methodOf ui.grid.validate.service:uiGridValidateService
29677 * @description Retrieves a validator by executing a validatorFactory
29678 * stored in an external service.
29679 * @param {string} name the name of the validator to retrieve
29680 * @param {object} argument an argument to pass to the validator factory
29681 */
29682 getValidatorFromExternalFactory: function(name, argument) {
29683 return service.externalFactoryFunction(name, argument).validatorFactory(argument);
29684 },
29685
29686 /**
29687 * @ngdoc service
29688 * @name getMessageFromExternalFactory
29689 * @methodOf ui.grid.validate.service:uiGridValidateService
29690 * @description Retrieves a message stored in an external service.
29691 * @param {string} name the name of the validator
29692 * @param {object} argument an argument to pass to the message function
29693 */
29694 getMessageFromExternalFactory: function(name, argument) {
29695 return service.externalFactoryFunction(name, argument).messageFunction(argument);
29696 },
29697
29698 /**
29699 * @ngdoc service
29700 * @name setValidator
29701 * @methodOf ui.grid.validate.service:uiGridValidateService
29702 * @description Adds a new validator to the service
29703 * @param {string} name the name of the validator, must be unique
29704 * @param {function} validatorFactory a factory that return a validatorFunction
29705 * @param {function} messageFunction a function that return the error message
29706 */
29707 setValidator: function(name, validatorFactory, messageFunction) {
29708 service.validatorFactories[name] = {
29709 validatorFactory: validatorFactory,
29710 messageFunction: messageFunction
29711 };
29712 },
29713
29714 /**
29715 * @ngdoc service
29716 * @name getValidator
29717 * @methodOf ui.grid.validate.service:uiGridValidateService
29718 * @description Returns a validator registered to the service
29719 * or retrieved from the external factory
29720 * @param {string} name the name of the validator to retrieve
29721 * @param {object} argument an argument to pass to the validator factory
29722 * @returns {object} the validator function
29723 */
29724 getValidator: function(name, argument) {
29725 if (service.externalFactoryFunction) {
29726 var validator = service.getValidatorFromExternalFactory(name, argument);
29727 if (validator) {
29728 return validator;
29729 }
29730 }
29731 if (!service.validatorFactories[name]) {
29732 throw ("Invalid validator name: " + name);
29733 }
29734 return service.validatorFactories[name].validatorFactory(argument);
29735 },
29736
29737 /**
29738 * @ngdoc service
29739 * @name getMessage
29740 * @methodOf ui.grid.validate.service:uiGridValidateService
29741 * @description Returns the error message related to the validator
29742 * @param {string} name the name of the validator
29743 * @param {object} argument an argument to pass to the message function
29744 * @returns {string} the error message related to the validator
29745 */
29746 getMessage: function(name, argument) {
29747 if (service.externalFactoryFunction) {
29748 var message = service.getMessageFromExternalFactory(name, argument);
29749 if (message) {
29750 return message;
29751 }
29752 }
29753 return service.validatorFactories[name].messageFunction(argument);
29754 },
29755
29756 /**
29757 * @ngdoc service
29758 * @name isInvalid
29759 * @methodOf ui.grid.validate.service:uiGridValidateService
29760 * @description Returns true if the cell (identified by rowEntity, colDef) is invalid
29761 * @param {object} rowEntity the row entity of the cell
29762 * @param {object} colDef the colDef of the cell
29763 * @returns {boolean} true if the cell is invalid
29764 */
29765 isInvalid: function (rowEntity, colDef) {
29766 return rowEntity['$$invalid'+colDef.name];
29767 },
29768
29769 /**
29770 * @ngdoc service
29771 * @name setInvalid
29772 * @methodOf ui.grid.validate.service:uiGridValidateService
29773 * @description Makes the cell invalid by adding the proper field to the entity
29774 * @param {object} rowEntity the row entity of the cell
29775 * @param {object} colDef the colDef of the cell
29776 */
29777 setInvalid: function (rowEntity, colDef) {
29778 rowEntity['$$invalid'+colDef.name] = true;
29779 },
29780
29781 /**
29782 * @ngdoc service
29783 * @name setValid
29784 * @methodOf ui.grid.validate.service:uiGridValidateService
29785 * @description Makes the cell valid by removing the proper error field from the entity
29786 * @param {object} rowEntity the row entity of the cell
29787 * @param {object} colDef the colDef of the cell
29788 */
29789 setValid: function (rowEntity, colDef) {
29790 delete rowEntity['$$invalid'+colDef.name];
29791 },
29792
29793 /**
29794 * @ngdoc service
29795 * @name setError
29796 * @methodOf ui.grid.validate.service:uiGridValidateService
29797 * @description Adds the proper error to the entity errors field
29798 * @param {object} rowEntity the row entity of the cell
29799 * @param {object} colDef the colDef of the cell
29800 * @param {string} validatorName the name of the validator that is failing
29801 */
29802 setError: function(rowEntity, colDef, validatorName) {
29803 if (!rowEntity['$$errors'+colDef.name]) {
29804 rowEntity['$$errors'+colDef.name] = {};
29805 }
29806 rowEntity['$$errors'+colDef.name][validatorName] = true;
29807 },
29808
29809 /**
29810 * @ngdoc service
29811 * @name clearError
29812 * @methodOf ui.grid.validate.service:uiGridValidateService
29813 * @description Removes the proper error from the entity errors field
29814 * @param {object} rowEntity the row entity of the cell
29815 * @param {object} colDef the colDef of the cell
29816 * @param {string} validatorName the name of the validator that is failing
29817 */
29818 clearError: function(rowEntity, colDef, validatorName) {
29819 if (!rowEntity['$$errors'+colDef.name]) {
29820 return;
29821 }
29822 if (validatorName in rowEntity['$$errors'+colDef.name]) {
29823 delete rowEntity['$$errors'+colDef.name][validatorName];
29824 }
29825 },
29826
29827 /**
29828 * @ngdoc function
29829 * @name getErrorMessages
29830 * @methodOf ui.grid.validate.service:uiGridValidateService
29831 * @description returns an array of i18n-ed error messages.
29832 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
29833 * @param {object} colDef the column whose errors we are looking for
29834 * @returns {array} An array of strings containing all the error messages for the cell
29835 */
29836 getErrorMessages: function(rowEntity, colDef) {
29837 var errors = [];
29838
29839 if (!rowEntity['$$errors'+colDef.name] || Object.keys(rowEntity['$$errors'+colDef.name]).length === 0) {
29840 return errors;
29841 }
29842
29843 Object.keys(rowEntity['$$errors'+colDef.name]).sort().forEach(function(validatorName) {
29844 errors.push(service.getMessage(validatorName, colDef.validators[validatorName]));
29845 });
29846
29847 return errors;
29848 },
29849
29850 /**
29851 * @ngdoc function
29852 * @name getFormattedErrors
29853 * @methodOf ui.grid.validate.service:uiGridValidateService
29854 * @description returns the error i18n-ed and formatted in html to be shown inside the page.
29855 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
29856 * @param {object} colDef the column whose errors we are looking for
29857 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
29858 * message inside the page (i.e. inside a div)
29859 */
29860 getFormattedErrors: function(rowEntity, colDef) {
29861 var msgString = "",
29862 errors = service.getErrorMessages(rowEntity, colDef);
29863
29864 if (!errors.length) {
29865 return;
29866 }
29867
29868 errors.forEach(function(errorMsg) {
29869 msgString += errorMsg + "<br/>";
29870 });
29871
29872 return $sce.trustAsHtml('<p><b>' + i18nService.getSafeText('validate.error') + '</b></p>' + msgString );
29873 },
29874
29875 /**
29876 * @ngdoc function
29877 * @name getTitleFormattedErrors
29878 * @methodOf ui.grid.validate.service:uiGridValidateService
29879 * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html
29880 * title attribute.
29881 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
29882 * @param {object} colDef the column whose errors we are looking for
29883 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
29884 * message inside an html title attribute
29885 */
29886 getTitleFormattedErrors: function(rowEntity, colDef) {
29887 var newLine = "\n",
29888 msgString = "",
29889 errors = service.getErrorMessages(rowEntity, colDef);
29890
29891 if (!errors.length) {
29892 return;
29893 }
29894
29895 errors.forEach(function(errorMsg) {
29896 msgString += errorMsg + newLine;
29897 });
29898
29899 return $sce.trustAsHtml(i18nService.getSafeText('validate.error') + newLine + msgString);
29900 },
29901
29902 /**
29903 * @ngdoc function
29904 * @name getTitleFormattedErrors
29905 * @methodOf ui.grid.validate.service:uiGridValidateService
29906 * @description Executes all validators on a cell (identified by row entity and column definition) and sets or clears errors
29907 * @param {object} rowEntity the row entity of the cell we want to run the validators on
29908 * @param {object} colDef the column definition of the cell we want to run the validators on
29909 * @param {object} newValue the value the user just entered
29910 * @param {object} oldValue the value the field had before
29911 */
29912 runValidators: function(rowEntity, colDef, newValue, oldValue, grid) {
29913 if (newValue === oldValue) {
29914 // If the value has not changed we perform no validation
29915 return;
29916 }
29917
29918 if (typeof(colDef.name) === 'undefined' || !colDef.name) {
29919 throw new Error('colDef.name is required to perform validation');
29920 }
29921
29922 service.setValid(rowEntity, colDef);
29923
29924 var validateClosureFactory = function(rowEntity, colDef, validatorName) {
29925 return function(value) {
29926 if (!value) {
29927 service.setInvalid(rowEntity, colDef);
29928 service.setError(rowEntity, colDef, validatorName);
29929 if (grid) {
29930 grid.api.validate.raise.validationFailed(rowEntity, colDef, newValue, oldValue);
29931 }
29932 }
29933 };
29934 };
29935
29936 var promises = [];
29937
29938 for (var validatorName in colDef.validators) {
29939 service.clearError(rowEntity, colDef, validatorName);
29940 var validatorFunction = service.getValidator(validatorName, colDef.validators[validatorName]);
29941
29942 // We pass the arguments as oldValue, newValue so they are in the same order
29943 // as ng-model validators (modelValue, viewValue)
29944 var promise = $q.when(validatorFunction(oldValue, newValue, rowEntity, colDef))
29945 .then(validateClosureFactory(rowEntity, colDef, validatorName));
29946
29947 promises.push(promise);
29948 }
29949
29950 return $q.all(promises);
29951 },
29952
29953 /**
29954 * @ngdoc function
29955 * @name createDefaultValidators
29956 * @methodOf ui.grid.validate.service:uiGridValidateService
29957 * @description adds the basic validators to the list of service validators
29958 */
29959 createDefaultValidators: function() {
29960 service.setValidator('minLength', function (argument) {
29961 return function (oldValue, newValue) {
29962 if (newValue === undefined || newValue === null || newValue === '') {
29963 return true;
29964 }
29965 return newValue.length >= argument;
29966 };
29967 }, function(argument) {
29968 return i18nService.getSafeText('validate.minLength').replace('THRESHOLD', argument);
29969 });
29970
29971 service.setValidator('maxLength', function (argument) {
29972 return function (oldValue, newValue) {
29973 if (newValue === undefined || newValue === null || newValue === '') {
29974 return true;
29975 }
29976 return newValue.length <= argument;
29977 };
29978 }, function(threshold) {
29979 return i18nService.getSafeText('validate.maxLength').replace('THRESHOLD', threshold);
29980 });
29981
29982 service.setValidator('required', function (argument) {
29983 return function (oldValue, newValue) {
29984 if (argument) {
29985 return !(newValue === undefined || newValue === null || newValue === '');
29986 }
29987 return true;
29988 };
29989 }, function() {
29990 return i18nService.getSafeText('validate.required');
29991 });
29992 },
29993
29994 initializeGrid: function (scope, grid) {
29995 grid.validate = {
29996
29997 isInvalid: service.isInvalid,
29998
29999 getErrorMessages: service.getErrorMessages,
30000
30001 getFormattedErrors: service.getFormattedErrors,
30002
30003 getTitleFormattedErrors: service.getTitleFormattedErrors,
30004
30005 runValidators: service.runValidators
30006 };
30007
30008 /**
30009 * @ngdoc object
30010 * @name ui.grid.validate.api:PublicApi
30011 *
30012 * @description Public Api for validation feature
30013 */
30014 var publicApi = {
30015 events: {
30016 validate: {
30017 /**
30018 * @ngdoc event
30019 * @name validationFailed
30020 * @eventOf ui.grid.validate.api:PublicApi
30021 * @description raised when one or more failure happened during validation
30022 * <pre>
30023 * gridApi.validate.on.validationFailed(scope, function(rowEntity, colDef, newValue, oldValue){...})
30024 * </pre>
30025 * @param {object} rowEntity the options.data element whose validation failed
30026 * @param {object} colDef the column whose validation failed
30027 * @param {object} newValue new value
30028 * @param {object} oldValue old value
30029 */
30030 validationFailed: function (rowEntity, colDef, newValue, oldValue) {
30031 }
30032 }
30033 },
30034 methods: {
30035 validate: {
30036 /**
30037 * @ngdoc function
30038 * @name isInvalid
30039 * @methodOf ui.grid.validate.api:PublicApi
30040 * @description checks if a cell (identified by rowEntity, colDef) is invalid
30041 * @param {object} rowEntity gridOptions.data[] array instance we want to check
30042 * @param {object} colDef the column whose errors we want to check
30043 * @returns {boolean} true if the cell value is not valid
30044 */
30045 isInvalid: function(rowEntity, colDef) {
30046 return grid.validate.isInvalid(rowEntity, colDef);
30047 },
30048 /**
30049 * @ngdoc function
30050 * @name getErrorMessages
30051 * @methodOf ui.grid.validate.api:PublicApi
30052 * @description returns an array of i18n-ed error messages.
30053 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
30054 * @param {object} colDef the column whose errors we are looking for
30055 * @returns {array} An array of strings containing all the error messages for the cell
30056 */
30057 getErrorMessages: function (rowEntity, colDef) {
30058 return grid.validate.getErrorMessages(rowEntity, colDef);
30059 },
30060 /**
30061 * @ngdoc function
30062 * @name getFormattedErrors
30063 * @methodOf ui.grid.validate.api:PublicApi
30064 * @description returns the error i18n-ed and formatted in html to be shown inside the page.
30065 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
30066 * @param {object} colDef the column whose errors we are looking for
30067 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
30068 * message inside the page (i.e. inside a div)
30069 */
30070 getFormattedErrors: function (rowEntity, colDef) {
30071 return grid.validate.getFormattedErrors(rowEntity, colDef);
30072 },
30073 /**
30074 * @ngdoc function
30075 * @name getTitleFormattedErrors
30076 * @methodOf ui.grid.validate.api:PublicApi
30077 * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html
30078 * title attribute.
30079 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
30080 * @param {object} colDef the column whose errors we are looking for
30081 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
30082 * message inside an html title attribute
30083 */
30084 getTitleFormattedErrors: function (rowEntity, colDef) {
30085 return grid.validate.getTitleFormattedErrors(rowEntity, colDef);
30086 }
30087 }
30088 }
30089 };
30090
30091 grid.api.registerEventsFromObject(publicApi.events);
30092 grid.api.registerMethodsFromObject(publicApi.methods);
30093
30094 if (grid.edit) {
30095 grid.api.edit.on.afterCellEdit(scope, function(rowEntity, colDef, newValue, oldValue) {
30096 grid.validate.runValidators(rowEntity, colDef, newValue, oldValue, grid);
30097 });
30098 }
30099
30100 service.createDefaultValidators();
30101 }
30102 };
30103
30104 return service;
30105 }]);
30106
30107 /**
30108 * @ngdoc directive
30109 * @name ui.grid.validate.directive:uiGridValidate
30110 * @element div
30111 * @restrict A
30112 * @description Adds validating features to the ui-grid directive.
30113 * @example
30114 <example module="app">
30115 <file name="app.js">
30116 var app = angular.module('app', ['ui.grid', 'ui.grid.edit', 'ui.grid.validate']);
30117
30118 app.controller('MainCtrl', ['$scope', function ($scope) {
30119 $scope.data = [
30120 { name: 'Bob', title: 'CEO' },
30121 { name: 'Frank', title: 'Lowly Developer' }
30122 ];
30123
30124 $scope.columnDefs = [
30125 {name: 'name', enableCellEdit: true, validators: {minLength: 3, maxLength: 9}, cellTemplate: 'ui-grid/cellTitleValidator'},
30126 {name: 'title', enableCellEdit: true, validators: {required: true}, cellTemplate: 'ui-grid/cellTitleValidator'}
30127 ];
30128 }]);
30129 </file>
30130 <file name="index.html">
30131 <div ng-controller="MainCtrl">
30132 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit ui-grid-validate></div>
30133 </div>
30134 </file>
30135 </example>
30136 */
30137
30138 module.directive('uiGridValidate', ['gridUtil', 'uiGridValidateService', function (gridUtil, uiGridValidateService) {
30139 return {
30140 priority: 0,
30141 replace: true,
30142 require: '^uiGrid',
30143 scope: false,
30144 compile: function () {
30145 return {
30146 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
30147 uiGridValidateService.initializeGrid($scope, uiGridCtrl.grid);
30148 },
30149 post: function ($scope, $elm, $attrs, uiGridCtrl) {
30150 }
30151 };
30152 }
30153 };
30154 }]);
30155 })();
30156
2651130157 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
2651230158 'use strict';
2651330159
2651430160 $templateCache.put('ui-grid/ui-grid-filter',
26515 "<div class=\"ui-grid-filter-container\" ng-repeat=\"colFilter in col.filters\" ng-class=\"{'ui-grid-filter-cancel-button-hidden' : colFilter.disableCancelFilterButton === true }\"><div ng-if=\"colFilter.type !== 'select'\"><input type=\"text\" class=\"ui-grid-filter-input ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-attr-placeholder=\"{{colFilter.placeholder || ''}}\" aria-label=\"{{colFilter.ariaLabel || aria.defaultFilterLabel}}\"><div role=\"button\" class=\"ui-grid-filter-button\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term !== null && colFilter.term !== ''\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\">&nbsp;</i></div></div><div ng-if=\"colFilter.type === 'select'\"><select class=\"ui-grid-filter-select ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-attr-placeholder=\"{{colFilter.placeholder || aria.defaultFilterLabel}}\" aria-label=\"{{colFilter.ariaLabel || ''}}\" ng-options=\"option.value as option.label for option in colFilter.selectOptions\"><option value=\"\"></option></select><div role=\"button\" class=\"ui-grid-filter-button-select\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term != null\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\">&nbsp;</i></div></div></div>"
30161 "<div class=\"ui-grid-filter-container\" ng-style=\"col.extraStyle\" ng-repeat=\"colFilter in col.filters\" ng-class=\"{'ui-grid-filter-cancel-button-hidden' : colFilter.disableCancelFilterButton === true }\"><div ng-if=\"colFilter.type !== 'select'\"><input type=\"text\" class=\"ui-grid-filter-input ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-attr-placeholder=\"{{colFilter.placeholder || ''}}\" aria-label=\"{{colFilter.ariaLabel || aria.defaultFilterLabel}}\"><div role=\"button\" class=\"ui-grid-filter-button\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term !== null && colFilter.term !== ''\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\">&nbsp;</i></div></div><div ng-if=\"colFilter.type === 'select'\"><select class=\"ui-grid-filter-select ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-show=\"colFilter.selectOptions.length > 0\" ng-attr-placeholder=\"{{colFilter.placeholder || aria.defaultFilterLabel}}\" aria-label=\"{{colFilter.ariaLabel || ''}}\" ng-options=\"option.value as option.label for option in colFilter.selectOptions\"><option value=\"\"></option></select><div role=\"button\" class=\"ui-grid-filter-button-select\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term != null\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\">&nbsp;</i></div></div></div>"
2651630162 );
2651730163
2651830164
2652630172 );
2652730173
2652830174
26529 $templateCache.put('ui-grid/ui-grid-group-panel',
26530 "<div class=\"ui-grid-group-panel\"><div ui-t=\"groupPanel.description\" class=\"description\" ng-show=\"groupings.length == 0\"></div><ul ng-show=\"groupings.length > 0\" class=\"ngGroupList\"><li class=\"ngGroupItem\" ng-repeat=\"group in configGroups\"><span class=\"ngGroupElement\"><span class=\"ngGroupName\">{{group.displayName}} <span ng-click=\"removeGroup($index)\" class=\"ngRemoveGroup\">x</span></span> <span ng-hide=\"$last\" class=\"ngGroupArrow\"></span></span></li></ul></div>"
26531 );
26532
26533
2653430175 $templateCache.put('ui-grid/ui-grid-header',
2653530176 "<div role=\"rowgroup\" class=\"ui-grid-header\"><!-- theader --><div class=\"ui-grid-top-panel\"><div class=\"ui-grid-header-viewport\"><div class=\"ui-grid-header-canvas\"><div class=\"ui-grid-header-cell-wrapper\" ng-style=\"colContainer.headerCellWrapperStyle()\"><div role=\"row\" class=\"ui-grid-header-cell-row\"><div class=\"ui-grid-header-cell ui-grid-clearfix\" ng-repeat=\"col in colContainer.renderedColumns track by col.uid\" ui-grid-header-cell col=\"col\" render-index=\"$index\"></div></div></div></div></div></div></div>"
2653630177 );
2653830179
2653930180 $templateCache.put('ui-grid/ui-grid-menu-button',
2654030181 "<div class=\"ui-grid-menu-button\"><div role=\"button\" ui-grid-one-bind-id-grid=\"'grid-menu'\" class=\"ui-grid-icon-container\" ng-click=\"toggleMenu()\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-menu\" ui-grid-one-bind-aria-label=\"i18n.aria.buttonLabel\">&nbsp;</i></div><div ui-grid-menu menu-items=\"menuItems\"></div></div>"
30182 );
30183
30184
30185 $templateCache.put('ui-grid/ui-grid-menu-header-item',
30186 "<li role=\"menuitem\"><div class=\"ui-grid-menu-item\" role=\"heading\" aria-level=\"2\" ng-show=\"itemShown()\"><i aria-hidden=\"true\">&nbsp; </i><span ng-bind=\"label()\"></span></div></li>"
2654130187 );
2654230188
2654330189
2656130207 " }\n" +
2656230208 "\n" +
2656330209 " .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
26564 " border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
30210 " border-bottom-width: {{ (((grid.getVisibleRowCount() * grid.options.rowHeight) < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
2656530211 " }\n" +
2656630212 "\n" +
2656730213 " {{ grid.verticalScrollbarStyles }}\n" +
2657330219 " }\n" +
2657430220 " */\n" +
2657530221 "\n" +
26576 " {{ grid.customStyles }}</style><div class=\"ui-grid-contents-wrapper\"><div ui-grid-menu-button ng-if=\"grid.options.enableGridMenu\"></div><div ng-if=\"grid.hasLeftContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'left'\"></div><div ui-grid-render-container container-id=\"'body'\" col-container-name=\"'body'\" row-container-name=\"'body'\" bind-scroll-horizontal=\"true\" bind-scroll-vertical=\"true\" enable-horizontal-scrollbar=\"grid.options.enableHorizontalScrollbar\" enable-vertical-scrollbar=\"grid.options.enableVerticalScrollbar\"></div><div ng-if=\"grid.hasRightContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'right'\"></div><div ui-grid-grid-footer ng-if=\"grid.options.showGridFooter\"></div><div ui-grid-column-menu ng-if=\"grid.options.enableColumnMenus\"></div><div ng-transclude></div></div></div>"
30222 " {{ grid.customStyles }}</style><div class=\"ui-grid-contents-wrapper\" role=\"grid\"><div ui-grid-menu-button ng-if=\"grid.options.enableGridMenu\"></div><div ng-if=\"grid.hasLeftContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'left'\"></div><div ui-grid-render-container container-id=\"'body'\" col-container-name=\"'body'\" row-container-name=\"'body'\" bind-scroll-horizontal=\"true\" bind-scroll-vertical=\"true\" enable-horizontal-scrollbar=\"grid.options.enableHorizontalScrollbar\" enable-vertical-scrollbar=\"grid.options.enableVerticalScrollbar\"></div><div ng-if=\"grid.hasRightContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'right'\"></div><div ui-grid-grid-footer ng-if=\"grid.options.showGridFooter\"></div><div ui-grid-column-menu ng-if=\"grid.options.enableColumnMenus\"></div><div ng-transclude></div></div></div>"
2657730223 );
2657830224
2657930225
2660330249
2660430250
2660530251 $templateCache.put('ui-grid/uiGridHeaderCell',
26606 "<div role=\"columnheader\" ng-class=\"{ 'sortable': sortable }\" ui-grid-one-bind-aria-labelledby-grid=\"col.uid + '-header-text ' + col.uid + '-sortdir-text'\" aria-sort=\"{{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending' : (!col.sort.direction ? 'none' : 'other'))}}\"><div role=\"button\" tabindex=\"0\" class=\"ui-grid-cell-contents ui-grid-header-cell-primary-focus\" col-index=\"renderIndex\" title=\"TOOLTIP\"><span class=\"ui-grid-header-cell-label\" ui-grid-one-bind-id-grid=\"col.uid + '-header-text'\">{{ col.displayName CUSTOM_FILTERS }}</span> <span ui-grid-one-bind-id-grid=\"col.uid + '-sortdir-text'\" ui-grid-visible=\"col.sort.direction\" aria-label=\"{{getSortDirectionAriaLabel()}}\"><i ng-class=\"{ 'ui-grid-icon-up-dir': col.sort.direction == asc, 'ui-grid-icon-down-dir': col.sort.direction == desc, 'ui-grid-icon-blank': !col.sort.direction }\" title=\"{{col.sort.priority ? i18n.headerCell.priority + ' ' + col.sort.priority : null}}\" aria-hidden=\"true\"></i> <sub class=\"ui-grid-sort-priority-number\">{{col.sort.priority}}</sub></span></div><div role=\"button\" tabindex=\"0\" ui-grid-one-bind-id-grid=\"col.uid + '-menu-button'\" class=\"ui-grid-column-menu-button\" ng-if=\"grid.options.enableColumnMenus && !col.isRowHeader && col.colDef.enableColumnMenu !== false\" ng-click=\"toggleMenu($event)\" ng-class=\"{'ui-grid-column-menu-button-last-col': isLastCol}\" ui-grid-one-bind-aria-label=\"i18n.headerCell.aria.columnMenuButtonLabel\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-angle-down\" aria-hidden=\"true\">&nbsp;</i></div><div ui-grid-filter></div></div>"
30252 "<div role=\"columnheader\" ng-class=\"{ 'sortable': sortable, 'ui-grid-header-cell-last-col': isLastCol }\" ui-grid-one-bind-aria-labelledby-grid=\"col.uid + '-header-text ' + col.uid + '-sortdir-text'\" aria-sort=\"{{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending' : (!col.sort.direction ? 'none' : 'other'))}}\"><div role=\"button\" tabindex=\"0\" ng-keydown=\"handleKeyDown($event)\" class=\"ui-grid-cell-contents ui-grid-header-cell-primary-focus\" col-index=\"renderIndex\" title=\"TOOLTIP\"><span class=\"ui-grid-header-cell-label\" ui-grid-one-bind-id-grid=\"col.uid + '-header-text'\">{{ col.displayName CUSTOM_FILTERS }}</span> <span ui-grid-one-bind-id-grid=\"col.uid + '-sortdir-text'\" ui-grid-visible=\"col.sort.direction\" aria-label=\"{{getSortDirectionAriaLabel()}}\"><i ng-class=\"{ 'ui-grid-icon-up-dir': col.sort.direction == asc, 'ui-grid-icon-down-dir': col.sort.direction == desc, 'ui-grid-icon-blank': !col.sort.direction }\" title=\"{{isSortPriorityVisible() ? i18n.headerCell.priority + ' ' + ( col.sort.priority + 1 ) : null}}\" aria-hidden=\"true\"></i> <sub ui-grid-visible=\"isSortPriorityVisible()\" class=\"ui-grid-sort-priority-number\">{{col.sort.priority + 1}}</sub></span></div><div role=\"button\" tabindex=\"0\" ui-grid-one-bind-id-grid=\"col.uid + '-menu-button'\" class=\"ui-grid-column-menu-button\" ng-if=\"grid.options.enableColumnMenus && !col.isRowHeader && col.colDef.enableColumnMenu !== false\" ng-click=\"toggleMenu($event)\" ng-keydown=\"headerCellArrowKeyDown($event)\" ui-grid-one-bind-aria-label=\"i18n.headerCell.aria.columnMenuButtonLabel\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-angle-down\" aria-hidden=\"true\">&nbsp;</i></div><div ui-grid-filter></div></div>"
2660730253 );
2660830254
2660930255
2661030256 $templateCache.put('ui-grid/uiGridMenu',
26611 "<div class=\"ui-grid-menu\" ng-if=\"shown\"><div class=\"ui-grid-menu-mid\" ng-show=\"shownMid\"><div class=\"ui-grid-menu-inner\"><button type=\"button\" ng-focus=\"focus=true\" ng-blur=\"focus=false\" class=\"ui-grid-menu-close-button\" ng-class=\"{'ui-grid-sr-only': (!focus)}\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"i18n.close\"></i></button><ul role=\"menu\" class=\"ui-grid-menu-items\"><li ng-repeat=\"item in menuItems\" role=\"menuitem\" ui-grid-menu-item ui-grid-one-bind-id=\"'menuitem-'+$index\" action=\"item.action\" name=\"item.title\" active=\"item.active\" icon=\"item.icon\" shown=\"item.shown\" context=\"item.context\" template-url=\"item.templateUrl\" leave-open=\"item.leaveOpen\" screen-reader-only=\"item.screenReaderOnly\"></li></ul></div></div></div>"
30257 "<div class=\"ui-grid-menu\" ng-show=\"shown\"><style ui-grid-style>{{dynamicStyles}}</style><div class=\"ui-grid-menu-mid\" ng-show=\"shownMid\"><div class=\"ui-grid-menu-inner\" ng-if=\"shown\"><ul role=\"menu\" class=\"ui-grid-menu-items\"><li ng-repeat=\"item in menuItems\" role=\"menuitem\" ui-grid-menu-item ui-grid-one-bind-id=\"'menuitem-'+$index\" action=\"item.action\" name=\"item.title\" active=\"item.active\" icon=\"item.icon\" shown=\"item.shown\" context=\"item.context\" template-url=\"item.templateUrl\" leave-open=\"item.leaveOpen\" screen-reader-only=\"item.screenReaderOnly\"></li></ul></div></div></div>"
2661230258 );
2661330259
2661430260
2661530261 $templateCache.put('ui-grid/uiGridMenuItem',
26616 "<button type=\"button\" class=\"ui-grid-menu-item\" ng-click=\"itemAction($event, title)\" ng-show=\"itemShown()\" ng-class=\"{ 'ui-grid-menu-item-active': active(), 'ui-grid-sr-only': (!focus && screenReaderOnly) }\" aria-pressed=\"{{active()}}\" tabindex=\"0\" ng-focus=\"focus=true\" ng-blur=\"focus=false\"><i ng-class=\"icon\" aria-hidden=\"true\">&nbsp;</i> {{ name }}</button>"
30262 "<button type=\"button\" class=\"ui-grid-menu-item\" ng-click=\"itemAction($event, title)\" ng-show=\"itemShown()\" ng-class=\"{ 'ui-grid-menu-item-active': active(), 'ui-grid-sr-only': (!focus && screenReaderOnly) }\" aria-pressed=\"{{active()}}\" tabindex=\"0\" ng-focus=\"focus=true\" ng-blur=\"focus=false\"><i ng-class=\"icon\" aria-hidden=\"true\">&nbsp; </i>{{ label() }}</button>"
2661730263 );
2661830264
2661930265
2662030266 $templateCache.put('ui-grid/uiGridRenderContainer',
26621 "<div role=\"grid\" ui-grid-one-bind-id-grid=\"'grid-container'\" class=\"ui-grid-render-container\" ng-style=\"{ 'margin-left': colContainer.getMargin('left') + 'px', 'margin-right': colContainer.getMargin('right') + 'px' }\"><!-- All of these dom elements are replaced in place --><div ui-grid-header></div><div ui-grid-viewport></div><div ng-if=\"colContainer.needsHScrollbarPlaceholder()\" class=\"ui-grid-scrollbar-placeholder\" ng-style=\"{height:colContainer.grid.scrollbarHeight + 'px'}\"></div><ui-grid-footer ng-if=\"grid.options.showColumnFooter\"></ui-grid-footer></div>"
30267 "<div role=\"presentation\" ui-grid-one-bind-id-grid=\"containerId + '-grid-container'\" class=\"ui-grid-render-container\" ng-style=\"{ 'margin-left': colContainer.getMargin('left') + 'px', 'margin-right': colContainer.getMargin('right') + 'px' }\"><!-- All of these dom elements are replaced in place --><div ui-grid-header></div><div ui-grid-viewport></div><div ng-if=\"colContainer.needsHScrollbarPlaceholder()\" class=\"ui-grid-scrollbar-placeholder\" ng-style=\"{height: colContainer.grid.scrollbarHeight + 'px'}\"></div><ui-grid-footer ng-if=\"grid.options.showColumnFooter\"></ui-grid-footer></div>"
2662230268 );
2662330269
2662430270
2664230288 );
2664330289
2664430290
30291 $templateCache.put('ui-grid/emptyBaseLayerContainer',
30292 "<div class=\"ui-grid-empty-base-layer-container ui-grid-canvas\"><div class=\"ui-grid-row\" ng-repeat=\"(rowRenderIndex, row) in grid.baseLayer.emptyRows track by $index\" ng-style=\"Viewport.rowStyle(rowRenderIndex)\"><div><div><div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class=\"ui-grid-cell {{ col.getColClass(false) }}\"></div></div></div></div></div>"
30293 );
30294
30295
2664530296 $templateCache.put('ui-grid/expandableRow',
2664630297 "<div ui-grid-expandable-row ng-if=\"expandableRow.shouldRenderExpand()\" class=\"expandableRow\" style=\"float:left; margin-top: 1px; margin-bottom: 1px\" ng-style=\"{width: (grid.renderContainers.body.getCanvasWidth()) + 'px', height: row.expandedRowHeight + 'px'}\"></div>"
2664730298 );
2664830299
2664930300
2665030301 $templateCache.put('ui-grid/expandableRowHeader',
26651 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !row.isExpanded, 'ui-grid-icon-minus-squared' : row.isExpanded }\" ng-click=\"grid.api.expandable.toggleRowExpansion(row.entity)\"></i></div></div>"
30302 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i class=\"clickable\" ng-if=\"!(row.groupHeader==true || row.entity.subGridOptions.disableRowExpandable)\" ng-class=\"{ 'ui-grid-icon-plus-squared' : !row.isExpanded, 'ui-grid-icon-minus-squared' : row.isExpanded }\" ng-click=\"grid.api.expandable.toggleRowExpansion(row.entity, $event)\"></i></div></div>"
2665230303 );
2665330304
2665430305
2665530306 $templateCache.put('ui-grid/expandableScrollFiller',
26656 "<div ng-if=\"expandableRow.shouldRenderFiller()\" ng-class=\"{scrollFiller:true, scrollFillerClass:(colContainer.name === 'body')}\" ng-style=\"{ width: (grid.getViewportWidth()) + 'px', height: row.expandedRowHeight + 2 + 'px', 'margin-left': grid.options.rowHeader.rowHeaderWidth + 'px' }\"><i class=\"ui-grid-icon-spin5 ui-grid-animate-spin\" ng-style=\"{'margin-top': ( row.expandedRowHeight/2 - 5) + 'px', 'margin-left' : ((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5) + 'px'}\"></i></div>"
30307 "<div ng-if=\"expandableRow.shouldRenderFiller()\" ng-class=\"{scrollFiller: true, scrollFillerClass:(colContainer.name === 'body')}\" ng-style=\"{ width: (grid.getViewportWidth()) + 'px', height: row.expandedRowHeight + 2 + 'px', 'margin-left': grid.options.rowHeader.rowHeaderWidth + 'px' }\">&nbsp;</div>"
2665730308 );
2665830309
2665930310
2666030311 $templateCache.put('ui-grid/expandableTopRowHeader',
26661 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !grid.expandable.expandedAll, 'ui-grid-icon-minus-squared' : grid.expandable.expandedAll }\" ng-click=\"grid.api.expandable.toggleAllRows()\"></i></div></div>"
30312 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><span class=\"ui-grid-cell-empty\" ng-if=\"!grid.options.showExpandAllButton\"></span> <button type=\"button\" class=\"ui-grid-icon-button clickable\" ng-if=\"grid.options.showExpandAllButton\" ng-class=\"{ 'ui-grid-icon-plus-squared' : !grid.expandable.expandedAll, 'ui-grid-icon-minus-squared' : grid.expandable.expandedAll }\" ng-click=\"grid.api.expandable.toggleAllRows()\"></button></div></div>"
2666230313 );
2666330314
2666430315
2667830329
2667930330
2668030331 $templateCache.put('ui-grid/pagination',
26681 "<div role=\"contentinfo\" class=\"ui-grid-pager-panel\" ui-grid-pager ng-show=\"grid.options.enablePaginationControls\"><div role=\"navigation\" class=\"ui-grid-pager-container\"><div role=\"menubar\" class=\"ui-grid-pager-control\"><button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-first\" ui-grid-one-bind-title=\"aria.pageToFirst\" ui-grid-one-bind-aria-label=\"aria.pageToFirst\" ng-click=\"pageFirstPageClick()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle\"><div class=\"first-bar\"></div></div></button> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-previous\" ui-grid-one-bind-title=\"aria.pageBack\" ui-grid-one-bind-aria-label=\"aria.pageBack\" ng-click=\"pagePreviousPageClick()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle prev-triangle\"></div></button> <input type=\"number\" ui-grid-one-bind-title=\"aria.pageSelected\" ui-grid-one-bind-aria-label=\"aria.pageSelected\" class=\"ui-grid-pager-control-input\" ng-model=\"grid.options.paginationCurrentPage\" min=\"1\" max=\"{{ paginationApi.getTotalPages() }}\" required> <span class=\"ui-grid-pager-max-pages-number\" ng-show=\"paginationApi.getTotalPages() > 0\"><abbr ui-grid-one-bind-title=\"paginationOf\">/</abbr> {{ paginationApi.getTotalPages() }}</span> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-next\" ui-grid-one-bind-title=\"aria.pageForward\" ui-grid-one-bind-aria-label=\"aria.pageForward\" ng-click=\"pageNextPageClick()\" ng-disabled=\"cantPageForward()\"><div class=\"last-triangle next-triangle\"></div></button> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-last\" ui-grid-one-bind-title=\"aria.pageToLast\" ui-grid-one-bind-aria-label=\"aria.pageToLast\" ng-click=\"pageLastPageClick()\" ng-disabled=\"cantPageToLast()\"><div class=\"last-triangle\"><div class=\"last-bar\"></div></div></button></div><div class=\"ui-grid-pager-row-count-picker\" ng-if=\"grid.options.paginationPageSizes.length > 1\"><select ui-grid-one-bind-aria-labelledby-grid=\"'items-per-page-label'\" ng-model=\"grid.options.paginationPageSize\" ng-options=\"o as o for o in grid.options.paginationPageSizes\"></select><span ui-grid-one-bind-id-grid=\"'items-per-page-label'\" class=\"ui-grid-pager-row-count-label\">&nbsp;{{sizesLabel}}</span></div><span ng-if=\"grid.options.paginationPageSizes.length <= 1\" class=\"ui-grid-pager-row-count-label\">{{grid.options.paginationPageSize}}&nbsp;{{sizesLabel}}</span></div><div class=\"ui-grid-pager-count-container\"><div class=\"ui-grid-pager-count\"><span ng-show=\"grid.options.totalItems > 0\">{{showingLow}} <abbr ui-grid-one-bind-title=\"paginationThrough\">-</abbr> {{showingHigh}} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
30332 "<div class=\"ui-grid-pager-panel\" ui-grid-pager ng-show=\"grid.options.enablePaginationControls\"><div role=\"navigation\" class=\"ui-grid-pager-container\"><div class=\"ui-grid-pager-control\"><button type=\"button\" class=\"ui-grid-pager-first\" ui-grid-one-bind-title=\"aria.pageToFirst\" ui-grid-one-bind-aria-label=\"aria.pageToFirst\" ng-click=\"pageFirstPageClick()\" ng-disabled=\"cantPageBackward()\"><div ng-class=\"grid.isRTL() ? 'last-triangle' : 'first-triangle'\"><div ng-class=\"grid.isRTL() ? 'last-bar-rtl' : 'first-bar'\"></div></div></button> <button type=\"button\" class=\"ui-grid-pager-previous\" ui-grid-one-bind-title=\"aria.pageBack\" ui-grid-one-bind-aria-label=\"aria.pageBack\" ng-click=\"pagePreviousPageClick()\" ng-disabled=\"cantPageBackward()\"><div ng-class=\"grid.isRTL() ? 'last-triangle prev-triangle' : 'first-triangle prev-triangle'\"></div></button> <input type=\"number\" ui-grid-one-bind-title=\"aria.pageSelected\" ui-grid-one-bind-aria-label=\"aria.pageSelected\" class=\"ui-grid-pager-control-input\" ng-model=\"grid.options.paginationCurrentPage\" min=\"1\" max=\"{{ paginationApi.getTotalPages() }}\" step=\"1\" required> <span class=\"ui-grid-pager-max-pages-number\" ng-show=\"paginationApi.getTotalPages() > 0\"><abbr ui-grid-one-bind-title=\"paginationOf\">/ </abbr>{{ paginationApi.getTotalPages() }} </span><button type=\"button\" class=\"ui-grid-pager-next\" ui-grid-one-bind-title=\"aria.pageForward\" ui-grid-one-bind-aria-label=\"aria.pageForward\" ng-click=\"pageNextPageClick()\" ng-disabled=\"cantPageForward()\"><div ng-class=\"grid.isRTL() ? 'first-triangle next-triangle' : 'last-triangle next-triangle'\"></div></button> <button type=\"button\" class=\"ui-grid-pager-last\" ui-grid-one-bind-title=\"aria.pageToLast\" ui-grid-one-bind-aria-label=\"aria.pageToLast\" ng-click=\"pageLastPageClick()\" ng-disabled=\"cantPageToLast()\"><div ng-class=\"grid.isRTL() ? 'first-triangle' : 'last-triangle'\"><div ng-class=\"grid.isRTL() ? 'first-bar-rtl' : 'last-bar'\"></div></div></button></div><div class=\"ui-grid-pager-row-count-picker\" ng-if=\"grid.options.paginationPageSizes.length > 1 && !grid.options.useCustomPagination\"><select ui-grid-one-bind-aria-labelledby-grid=\"'items-per-page-label'\" ng-model=\"grid.options.paginationPageSize\" ng-options=\"o as o for o in grid.options.paginationPageSizes\"></select><span ui-grid-one-bind-id-grid=\"'items-per-page-label'\" class=\"ui-grid-pager-row-count-label\">&nbsp;{{sizesLabel}}</span></div><span ng-if=\"grid.options.paginationPageSizes.length <= 1\" class=\"ui-grid-pager-row-count-label\">{{grid.options.paginationPageSize}}&nbsp;{{sizesLabel}}</span></div><div class=\"ui-grid-pager-count-container\"><div class=\"ui-grid-pager-count\"><span ng-show=\"grid.options.totalItems > 0\">{{ 1 + paginationApi.getFirstRowIndex() }} <abbr ui-grid-one-bind-title=\"paginationThrough\">- </abbr>{{ 1 + paginationApi.getLastRowIndex() }} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
2668230333 );
2668330334
2668430335
2669330344
2669430345
2669530346 $templateCache.put('ui-grid/selectionHeaderCell',
26696 "<div><!-- <div class=\"ui-grid-vertical-bar\">&nbsp;</div> --><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-selection-select-all-buttons ng-if=\"grid.options.enableSelectAll\"></ui-grid-selection-select-all-buttons></div></div>"
30347 "<div><!-- <div class=\"ui-grid-vertical-bar\">&nbsp;</div> --><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-selection-select-all-buttons ng-if=\"grid.options.enableSelectAll\" role=\"checkbox\" ng-model=\"grid.selection.selectAll\"></ui-grid-selection-select-all-buttons></div></div>"
2669730348 );
2669830349
2669930350
2670030351 $templateCache.put('ui-grid/selectionRowHeader',
26701 "<div class=\"ui-grid-disable-selection\"><div class=\"ui-grid-cell-contents\"><ui-grid-selection-row-header-buttons></ui-grid-selection-row-header-buttons></div></div>"
30352 "<div class=\"ui-grid-cell-contents ui-grid-disable-selection clickable\"><ui-grid-selection-row-header-buttons></ui-grid-selection-row-header-buttons></div>"
2670230353 );
2670330354
2670430355
2670530356 $templateCache.put('ui-grid/selectionRowHeaderButtons',
26706 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-row-selected': row.isSelected}\" ng-click=\"selectButtonClick(row, $event)\">&nbsp;</div>"
30357 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok clickable\" ng-class=\"{'ui-grid-row-selected': row.isSelected}\" ng-click=\"selectButtonClick(row, $event)\" ng-keydown=\"selectButtonKeyDown(row, $event)\" role=\"checkbox\" ng-model=\"row.isSelected\">&nbsp;</div>"
2670730358 );
2670830359
2670930360
2671030361 $templateCache.put('ui-grid/selectionSelectAllButtons',
26711 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-all-selected': grid.selection.selectAll}\" ng-click=\"headerButtonClick($event)\"></div>"
30362 "<div role=\"button\" class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-all-selected': grid.selection.selectAll}\" ng-click=\"headerButtonClick($event)\" ng-keydown=\"headerButtonKeyDown($event)\"></div>"
2671230363 );
2671330364
2671430365
2671530366 $templateCache.put('ui-grid/treeBaseExpandAllButtons',
26716 "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-icon-minus-squared': grid.treeBase.numberLevels > 0 && grid.treeBase.expandAll, 'ui-grid-icon-plus-squared': grid.treeBase.numberLevels > 0 && !grid.treeBase.expandAll}\" ng-click=\"headerButtonClick($event)\"></div>"
30367 "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"headerButtonClass()\" ng-click=\"headerButtonClick($event)\"></div>"
2671730368 );
2671830369
2671930370
2672030371 $templateCache.put('ui-grid/treeBaseHeaderCell',
26721 "<div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-tree-base-expand-all-buttons></ui-grid-tree-base-expand-all-buttons></div></div>"
30372 "<div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-tree-base-expand-all-buttons ng-if=\"grid.options.enableExpandAll\"></ui-grid-tree-base-expand-all-buttons></div></div>"
2672230373 );
2672330374
2672430375
2672830379
2672930380
2673030381 $templateCache.put('ui-grid/treeBaseRowHeaderButtons',
26731 "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-tree-base-header': row.treeLevel > -1 }\" ng-click=\"treeButtonClick(row, $event)\"><i ng-class=\"{'ui-grid-icon-minus-squared': ( ( grid.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) && row.treeNode.state === 'expanded', 'ui-grid-icon-plus-squared': ( ( grid.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) && row.treeNode.state === 'collapsed'}\" ng-style=\"{'padding-left': grid.options.treeIndent * row.treeLevel + 'px'}\"></i> &nbsp;</div>"
30382 "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-tree-base-header': row.treeLevel > -1 }\" ng-click=\"treeButtonClick(row, $event)\"><i ng-class=\"treeButtonClass(row)\" ng-style=\"{'padding-left': grid.options.treeIndent * row.treeLevel + 'px'}\"></i> &nbsp;</div>"
2673230383 );
2673330384
26734 }]);
30385
30386 $templateCache.put('ui-grid/cellTitleValidator',
30387 "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" title=\"{{grid.validate.getTitleFormattedErrors(row.entity,col.colDef)}}\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
30388 );
30389
30390
30391 $templateCache.put('ui-grid/cellTooltipValidator',
30392 "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" tooltip-html-unsafe=\"{{grid.validate.getFormattedErrors(row.entity,col.colDef)}}\" tooltip-enable=\"grid.validate.isInvalid(row.entity,col.colDef)\" tooltip-append-to-body=\"true\" tooltip-placement=\"top\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
30393 );
30394
30395 }]);
281281 templateUrl: 'scripts/auth/partials/forbidden.html',
282282 title: ' Forbidden |'
283283 }).
284 when('/workspace-worth/ws/:wsId', {
285 templateUrl: 'scripts/dashboard/partials/vulns-by-price.html',
286 controller: 'vulnsByPriceCtrl',
287 title: ' Workspace worth |'
288 }).
284289 otherwise({
285290 templateUrl: 'scripts/commons/partials/home.html',
286291 controller: 'homeCtrl'
9797 var oldName = $scope.workspace;
9898 var modal = $uibModal.open({
9999 templateUrl: 'scripts/workspaces/partials/modalEdit.html',
100 backdrop : 'static',
100101 controller: 'workspacesModalEdit',
101102 size: 'lg',
102103 resolve: {
262262 return get(getUrl, data);
263263 }
264264
265 ServerAPI.getActivityFeed = function(wsName, data) {
266 var getUrl = createGetUrl(wsName, 'commands') + 'activity_feed/';
265 ServerAPI.getActivityFeed = function(wsName, data, onlyLastActivities) {
266 var getUrl = createGetUrl(wsName, 'activities') + (onlyLastActivities ? '?page_size=15&page=1' : '');
267267 return get(getUrl, data);
268268 }
269269
6363
6464 var loadCredentials = function (credentials){
6565 credentials.forEach(function(cred){
66
66
6767 var object = new credential(cred.value, cred.value.parent, cred.value.parent_type);
6868 object.getParentName($scope.workspace).then(function(response){
6969 object.target = response;
7474 };
7575
7676 var getAndLoadCredentials = function() {
77
77
7878 // Load all credentials, we dont have a parent.
7979 if($scope.parentObject.parent_type === undefined){
8080 ServerAPI.getCredentials($scope.workspace).then(function(response){
121121 var removeFromView = function(credential){
122122 $scope.credentials.forEach(function(item, index){
123123 if (item._id === credential._id)
124 $scope.credentials.splice(index, 1);
124 $scope.credentials.splice(index, 1);
125125 });
126126 };
127127
152152 // Add parent id, create credential and save to server.
153153 try {
154154 var credentialObj = new credential(credentialData, parent_id, parent_type);
155
155
156156 credentialObj.create($scope.workspace).then(function(){
157157 $scope.credentials.push(credentialObj);
158158 }, function(){
179179 $scope.new = function() {
180180 var modal = $uibModal.open({
181181 templateUrl: 'scripts/credentials/partials/modalNewEdit.html',
182 backdrop : 'static',
182183 controller: 'modalNewEditCredentialCtrl',
183184 size: 'lg',
184185 resolve: {
200201 $scope.edit = function() {
201202
202203 var credentialToEdit = $scope.selectedCredentials()[0];
203
204
204205 var modal = $uibModal.open({
205206 templateUrl: 'scripts/credentials/partials/modalNewEdit.html',
207 backdrop : 'static',
206208 controller: 'modalNewEditCredentialCtrl',
207209 size: 'lg',
208210 resolve: {
66 angular.module('faradayApp')
77 .controller('activityFeedCtrl',
88 ['$scope', '$routeParams', 'dashboardSrv',
9 function($scope, $routeParams, dashboardSrv) {
10
11 var vm = this;
12 vm.commands = [];
9 function ($scope, $routeParams, dashboardSrv) {
1310
14 // Get last 5 commands
15 var init = function() {
16 if($routeParams.wsId != undefined) {
17 $scope.workspace = $routeParams.wsId;
11 var vm = this;
12 vm.commands = [];
1813
19 dashboardSrv.getActivityFeed($scope.workspace)
20 .then(function(commands) {
21 vm.commands = commands;
22 });
23 }
24 };
14 // Get last 15 commands
15 var init = function () {
16 if ($routeParams.wsId !== undefined) {
17 $scope.workspace = $routeParams.wsId;
2518
26 dashboardSrv.registerCallback(init);
27 init();
28 }]);
19 $scope.isExpanded = false;
20 $scope.hideEmpty = false;
21
22 collapse();
23
24 dashboardSrv.getActivityFeed($scope.workspace)
25 .then(function (response) {
26 vm.commands = response.activities;
27 });
28 }
29 };
30
31 $scope.toggleExpanded = function () {
32 if ($scope.isExpanded) {
33 collapse();
34 } else {
35 expand();
36 }
37 };
38
39 var collapse = function () {
40 $scope.cmdLimit = 5;
41 $scope.isExpanded = false;
42 $scope.hideEmpty = false;
43 angular.element('#first-row-panel').css('display', 'inherit');
44 angular.element('#activities-container-row').addClass('mt-md');
45 };
46
47 var expand = function () {
48 $scope.cmdLimit = 15; // Should be a constant
49 $scope.isExpanded = true;
50 angular.element('#first-row-panel').css('display', 'none');
51 angular.element('#activities-container-row').removeClass('mt-md');
52 };
53
54 $scope.isEmpty = function (cmd) {
55 return cmd.hosts_count === 0 && cmd.services_count === 0 && cmd.vulnerabilities_count === 0;
56 };
57
58 dashboardSrv.registerCallback(init);
59 init();
60 }]);
00 <article class='panel left-big-box' ng-controller="activityFeedCtrl as activityFeed">
1 <header>
1 <header ng-init="cmdLimit = 5">
22 <h2>Activity Feed
33 <span class="glyphicon glyphicon-info-sign" uib-tooltip="Faraday feed"></span>
4
5 <span class="pull-right" ng-show="!isExpanded && activityFeed.commands.length > 5">
6 <img src="images/icon_viewmore.svg" class="feed-icon"/>
7 <a href="javascript:;" ng-click="toggleExpanded()" >View more</a>
8 </span>
9
10 <span class="pull-right" ng-show="isExpanded">
11 <img src="images/icon_viewless.svg" class="feed-icon"/>
12 <a href="javascript:;" ng-click="toggleExpanded()" >View less</a>
13 </span>
14
15 <!--TODO: Change icon-->
16 <span class="pull-right margin-right-15px" ng-show="isExpanded && !hideEmpty">
17 <!--<img src="images/icon_viewless.svg" class="feed-icon"/> -->
18 <a href="javascript:;" ng-click="hideEmpty = true" >Hide empty</a>
19 </span>
20
21 <!--TODO: Change icon-->
22 <span class="pull-right margin-right-15px" ng-show="isExpanded && hideEmpty">
23 <!--<img src="images/icon_viewless.svg" class="feed-icon"/>-->
24 <a href="javascript:;" ng-click="hideEmpty = false" >Show empty</a>
25 </span>
26
27
428 </h2>
29
30
531 </header>
632 <div ng-if="activityFeed.commands.length === 0" class="no-info-overlay">
733 <p class="no-info-text">No activities found yet.</p>
935 <div class="ph-xl">
1036 <table id="commands" ng-if="activityFeed.commands.length > 0" class="tablesorter table table-striped">
1137 <tbody>
12 <tr ng-repeat="cmd in activityFeed.commands | orderBy : '-date' | limitTo:5">
13 <td align="left">
14 <span ng-if="cmd.import_source == 'report'" class="fa fa-upload"/>
15 <span ng-if="cmd.import_source == 'shell'" class="fa fa-terminal"/>
16 <b class="capitalize fg-blue">{{cmd.user || Anonymous}}</b>
17 <span ng-if="cmd.import_source == 'shell'" uib-tooltip="{{ cmd.command + ' ' + cmd.params}}">ran <strong>{{cmd.command}}</strong></span>
18 <span ng-if="cmd.import_source == 'report'" uib-tooltip="Import {{ cmd.command + ': ' + cmd.params}}">imported <strong>{{cmd.tool == 'unknown' ? cmd.command : cmd.tool}}</strong> report</span>
19 <span> and found</span>
20 <span ng-if="cmd.hosts_count == 0 && cmd.services_count == 0 && cmd.vulnerabilities_count == 0 "> nothing</span>
21 <span ng-if="cmd.hosts_count != 0 || cmd.services_count != 0 || cmd.vulnerabilities_count != 0 ">: </span>
22 <span ng-if="cmd.hosts_count > 0">{{cmd.hosts_count}} {{cmd.hosts_count == 1 ? 'host' : 'hosts'}}</span>
23 <span ng-if="cmd.hosts_count != 0 && cmd.services_count != 0 && cmd.vulnerabilities_count != 0">, </span>
24 <span ng-if="(cmd.hosts_count != 0 && cmd.services_count != 0 && cmd.vulnerabilities_count == 0) || (cmd.hosts_count != 0 && cmd.services_count == 0 && cmd.vulnerabilities_count != 0)"> & </span>
25 <span ng-if="cmd.services_count > 0">{{cmd.services_count}} {{cmd.services_count == 1 ? 'service' : 'services'}}</span>
26 <span ng-if="(cmd.hosts_count != 0 && cmd.services_count != 0 && cmd.vulnerabilities_count != 0) || (cmd.hosts_count == 0 && cmd.services_count != 0 && cmd.vulnerabilities_count != 0)"> & </span>
27 <span ng-if="cmd.vulnerabilities_count > 0"><a ng-click="navigate('/status/ws/' + workspace + '/search/command_id=' + cmd._id)"> {{cmd.vulnerabilities_count}} {{cmd.vulnerabilities_count == 1 ? 'vulnerability' : 'vulnerabilities'}}</a></span>
28 <span ng-if="cmd.criticalIssue > 0">- {{cmd.criticalIssue}} {{cmd.criticalIssue == 1 ? 'is' : 'are'}} rated as <b>Critical</b>.</span>
29 <span class ="small-size fg-light-gray" am-time-ago="cmd.date" am-preprocess="utc"/>
30 </td>
31 </tr>
38 <tr ng-repeat="cmd in activityFeed.commands | orderBy : '-date' | limitTo:cmdLimit"
39 ng-show="!isEmpty(cmd) || !hideEmpty">
40 <td align="left" ng-class="{'hide-border-top':$first}">
41 <span ng-if="cmd.import_source == 'report'">
42 <img src="images/icon_terminal.svg" class="feed-icon"/>
43 </span>
44 <span ng-if="cmd.import_source == 'shell'">
45 <img src="images/icon_terminal.svg" class="feed-icon"/>
46 </span>
47
48 <b class="capitalize fg-blue">{{cmd.user || Anonymous}}</b>
49 <span ng-if="cmd.import_source == 'shell'"
50 uib-tooltip="{{ cmd.command + ' ' + cmd.params}}"><span class="little-gray">ran</span> <strong>{{cmd.command}}</strong></span>
51 <span ng-if="cmd.import_source == 'report'"
52 uib-tooltip="Import {{ cmd.command + ': ' + cmd.params}}">imported <strong>{{cmd.tool == 'unknown' ? cmd.command : cmd.tool}}</strong> report</span>
53 <span class="little-gray"> and found</span>
54 <span ng-if="cmd.hosts_count == 0 && cmd.services_count == 0 && cmd.vulnerabilities_count == 0 "> <strong>nothing</strong></span>
55 <span ng-if="cmd.hosts_count != 0 || cmd.services_count != 0 || cmd.vulnerabilities_count != 0 ">: </span>
56 <span ng-if="cmd.hosts_count > 0">{{cmd.hosts_count}} {{cmd.hosts_count == 1 ? 'host' : 'hosts'}}</span>
57 <span ng-if="cmd.hosts_count != 0 && cmd.services_count != 0 && cmd.vulnerabilities_count != 0">, </span>
58 <span ng-if="(cmd.hosts_count != 0 && cmd.services_count != 0 && cmd.vulnerabilities_count == 0) || (cmd.hosts_count != 0 && cmd.services_count == 0 && cmd.vulnerabilities_count != 0)"> & </span>
59 <span ng-if="cmd.services_count > 0">{{cmd.services_count}} {{cmd.services_count == 1 ? 'service' : 'services'}}</span>
60 <span ng-if="(cmd.hosts_count != 0 && cmd.services_count != 0 && cmd.vulnerabilities_count != 0) || (cmd.hosts_count == 0 && cmd.services_count != 0 && cmd.vulnerabilities_count != 0)"> & </span>
61 <span ng-if="cmd.vulnerabilities_count > 0"><a
62 ng-click="navigate('/status/ws/' + workspace + '/search/command_id=' + cmd._id)"> {{cmd.vulnerabilities_count}} {{cmd.vulnerabilities_count == 1 ? 'vulnerability' : 'vulnerabilities'}}</a></span>
63 <span ng-if="cmd.criticalIssue > 0">- {{cmd.criticalIssue}} {{cmd.criticalIssue == 1 ? 'is' : 'are'}} rated as <b>Critical</b>.</span>
64 <span class="small-size fg-light-gray" am-time-ago="cmd.date" am-preprocess="utc"/>
65 </td>
66 </tr>
3267 </tbody>
3368 </table>
3469 </div>
77 <div ng-controller="headerCtrl" ng-include="'scripts/commons/partials/header.html'"></div>
88 <div class="row ph-xl m0">
99 <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12 ">
10 <div class="row">
10 <div class="row" id="first-row-panel">
1111 <div class="col-lg-4 col-md-4 col-sm-12 col-xs-12 pl0 pr-sm">
1212 <div ng-include="'scripts/dashboard/partials/workspace-progress.html'"></div>
1313 </div>
1818 <div ng-include="'scripts/dashboard/partials/vulns-by-status.html'"></div>
1919 </div>
2020 </div>
21 <div class="row mt-md">
21 <div class="row mt-md" id="activities-container-row">
2222 <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 pl0 pr-sm">
2323 <div ng-include="'scripts/dashboard/partials/activityFeed.html'"></div>
2424 </div>
2626 <div class="row">
2727 <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 pl0 pr-sm">
2828 <div ng-include="'scripts/dashboard/partials/last-vulns.html'"></div>
29 </div>
30 </div>
31 <div class="row">
32 <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 pl0 pr-sm">
33 <div ng-include="'scripts/dashboard/partials/vulns-by-price.html'"></div>
3429 </div>
3530 </div>
3631 </div>
0 <article class='panel left-big-box' ng-controller="lastVulnsCtrl">
0 <article class='panel left-big-box' ng-controller="lastVulnsCtrl" id="last-vuln-panel">
11 <header>
22 <h2>Last Vulnerabilities
33 <span class="glyphicon glyphicon-info-sign" uib-tooltip="Last vulnerabilities added"></span>
0 <article id="vulns-by-price" class="panel" ng-controller="vulnsByPriceCtrl">
1 <header>
2 <h2>
3 Workspace's worth
4 <span class="glyphicon glyphicon-info-sign" uib-tooltip="Total net worth of Workspace, according to current vulnerabilities prices"></span>
5 </h2>
6 </header>
7 <div ng-if="workspaceWorth === 0" class="no-info-overlay">
8 <p class="no-info-text">No vulnerabilities found yet.</p>
9 </div>
10 <div class="main" ng-if="workspaceWorth > 0">
11 <div class="center-lg-6"><h4><i class="fa fa-money"></i> {{workspaceWorth | currency}} total</h4></div>
12 <div d3-horizontal-stacked-bar data="vulns" class="stackedbars"></div>
13 <div id="vulns-by-price-reference" class="center-lg-6">
14 <ul class="label-list">
15 <li ng-repeat="(severity, price) in vulnPrices" uib-tooltip="Click on number to edit price">
16 <span class="label vuln fondo-{{severity}}">
17 {{severity}} $
18 <span contenteditable="true" ng-model="vulnPrices[severity]"></span>
19 </span>
20 </li>
21 </ul>
22 </div><!-- #vulns-by-price-reference .center-lg-6 -->
23 </div>
24 </article>
0 <section id="main" class="seccion clearfix dashboard">
1 <div class="faraday-header-border-fix"></div>
2 <div class="right-main">
3 <div ng-controller="headerCtrl" ng-include="'scripts/commons/partials/header.html'"></div>
4 <article id="vulns-by-price" class="panel" ng-controller="vulnsByPriceCtrl">
5 <header>
6 <h2>
7 Workspace's worth
8 <span class="glyphicon glyphicon-info-sign" uib-tooltip="Total net worth of Workspace, according to current vulnerabilities prices"></span>
9 </h2>
10 </header>
11 <div ng-if="workspaceWorth === 0" class="no-info-overlay">
12 <p class="no-info-text">No vulnerabilities found yet.</p>
13 </div>
14 <div class="main" ng-if="workspaceWorth > 0">
15 <div class="center-lg-6"><h4><i class="fa fa-money"></i> {{workspaceWorth | currency}} total</h4></div>
16 <div d3-horizontal-stacked-bar data="vulns" class="stackedbars"></div>
17 <div id="vulns-by-price-reference" class="center-lg-6">
18 <ul class="label-list">
19 <li ng-repeat="(severity, price) in vulnPrices" uib-tooltip="Click on number to edit price">
20 <span class="label vuln fondo-{{severity}}">
21 {{severity}} $
22 <span contenteditable="true" ng-model="vulnPrices[severity]"></span>
23 </span>
24 </li>
25 </ul>
26 </div><!-- #vulns-by-price-reference .center-lg-6 -->
27 </div>
28 </article>
29 </div>
30 </section>
243243 dashboardSrv.getActivityFeed = function(ws) {
244244 var deferred = $q.defer();
245245
246 ServerAPI.getActivityFeed(ws).then(function(res) {
246 ServerAPI.getActivityFeed(ws, undefined, true).then(function(res) {
247247 deferred.resolve(res.data);
248248 }, function() {
249249 deferred.reject();
234234 $scope.new = function() {
235235 var modal = $uibModal.open({
236236 templateUrl: 'scripts/services/partials/modalNew.html',
237 backdrop : 'static',
237238 controller: 'serviceModalNew',
238239 size: 'lg',
239240 resolve: {
257258 if($scope.selectedServices().length > 0) {
258259 var modal = $uibModal.open({
259260 templateUrl: 'scripts/services/partials/modalEdit.html',
261 backdrop : 'static',
260262 controller: 'serviceModalEdit',
261263 size: 'lg',
262264 resolve: {
120120 $scope.new = function() {
121121 var modal = $uibModal.open({
122122 templateUrl: 'scripts/licenses/partials/modalNew.html',
123 backdrop : 'static',
123124 controller: 'licensesModalNew',
124125 size: 'lg',
125126 resolve: {}
143144 var license = $scope.selectedLicenses()[0];
144145 var modal = $uibModal.open({
145146 templateUrl: 'scripts/licenses/partials/modalEdit.html',
147 backdrop : 'static',
146148 controller: 'licensesModalEdit',
147149 size: 'lg',
148150 resolve: {
177177 "severity": true,
178178 "service": true,
179179 "target": true,
180 "host_os": false,
180181 "desc": true,
181182 "resolution": false,
182183 "data": false,
209210 "service": "110",
210211 "hostnames": "130",
211212 "target": "100",
213 "host_os": "300",
212214 "desc": "600",
213215 "resolution": "170",
214216 "data": "170",
326328 sort: getColumnSort('target'),
327329 visible: $scope.columns["target"]
328330 });
331 $scope.gridOptions.columnDefs.push({ name : 'host_os',
332 displayName: "host_os", // Don't touch this! It will break everything. Seriously
333 cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/hostoscolumn.html',
334 headerCellTemplate: header,
335 sort: getColumnSort('host_os'),
336 visible: $scope.columns["host_os"]
337 });
329338 $scope.gridOptions.columnDefs.push({ name : 'desc',
330339 cellTemplate: 'scripts/statusReport/partials/ui-grid/columns/desccolumn.html',
331340 headerCellTemplate: header,
536545 return obj.data;
537546 });
538547
539 return response.filter(function(x){!angular.equals(x, {})});
548 return response.filter(function(x){
549 return !angular.equals(x["exploitdb"], []) && !angular.equals(x["metasploit"], [])
550 });
540551
541552 }, function(failed) {
542553 commonsFact.showMessage("Something failed searching vulnerability exploits.");
758769 if (vulns.length == 1) {
759770 var modal = $uibModal.open({
760771 templateUrl: 'scripts/statusReport/partials/modalEdit.html',
772 backdrop : 'static',
761773 controller: 'modalEditCtrl as modal',
762774 size: 'lg',
763775 resolve: {
10201032 $scope.new = function() {
10211033 var modal = $uibModal.open({
10221034 templateUrl: 'scripts/statusReport/partials/modalNew.html',
1035 backdrop : 'static',
10231036 controller: 'modalNewVulnCtrl as modal',
10241037 size: 'lg',
10251038 resolve: {
0 <div><div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space" uib-tooltip="{{COL_FIELD}}">{{COL_FIELD CUSTOM_FILTERS | removeLinebreaks}}</div></div>
0 <div><div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space" >{{COL_FIELD CUSTOM_FILTERS | removeLinebreaks}}</div></div>
0 <div ng-if='row.entity._id != undefined' class='ui-grid-cell-contents row-tooltip'><a ng-href="{{grid.appScope.hash}}/search/host_os={{row.entity.host_os}}">{{COL_FIELD CUSTOM_FILTERS}}</a></div><div ng-if="row.groupHeader && col.grouping.groupPriority !== undefined" class="ui-grid-cell-contents white-space">{{COL_FIELD CUSTOM_FILTERS}}</div>
00 <div ng-if="row.entity._id != undefined">
1 <div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space" uib-tooltip="{{COL_FIELD}}"><a ng-href="{{grid.appScope.hash}}/search/name={{grid.appScope.encodeUrl(row.entity.name)}}">{{COL_FIELD CUSTOM_FILTERS}}</a>
1 <div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents white-space"><a ng-href="{{grid.appScope.hash}}/search/name={{grid.appScope.encodeUrl(row.entity.name)}}">{{COL_FIELD CUSTOM_FILTERS}}</a>
22 </div>
33 </div>
4 <div ng-if="row.groupHeader && col.grouping.groupPriority !== undefined" class="ui-grid-cell-contents white-space">{{COL_FIELD CUSTOM_FILTERS}}</div>
4 <div ng-if="row.groupHeader && col.grouping.groupPriority !== undefined" class="ui-grid-cell-contents white-space">{{COL_FIELD CUSTOM_FILTERS}}</div>
3838 opacity: 0.5;
3939 }
4040
41 .status-report-grid .grid_id .ui-grid-cell-contents{
41 .status-report-grid .grid_id .ui-grid-cell-contents{
4242 position: relative;
4343 top: 3px;
4444 }
7878
7979 .hosts-list .ui-grid-header .hosts-list-checkall{
8080 position: relative;
81 top: 3px;
8281 text-align: center;
8382 }
8483
227226 }
228227
229228 .control-wrapper button{
230 border-radius: 0px;
229 border-radius: 0px;
231230 }
232231
233232 .control-wrapper > button{
234 border: none;
233 border: none;
235234 background: none;
236235 width: 47px;
237236 height: 33px;
235235 $scope.new = function() {
236236 var modal = $uibModal.open({
237237 templateUrl: 'scripts/vulndb/partials/modalNew.html',
238 backdrop : 'static',
238239 controller: 'vulnModelModalNew',
239240 size: 'lg',
240241 resolve: {}
264265 var model = $scope.selectedModels()[0];
265266 var modal = $uibModal.open({
266267 templateUrl: 'scripts/vulndb/partials/modalEdit.html',
268 backdrop : 'static',
267269 controller: 'vulndDbModalEdit',
268270 size: 'lg',
269271 resolve: {
4141 this.service = "";
4242 this.severity = "";
4343 this.target = "";
44 this.host_os = "";
4445 this.type = "Vulnerability";
4546 this.ws = "";
4647 this.status = "opened";
7273 if(data._id !== undefined) self._id = data._id;
7374 if(data.metadata !== undefined) self.metadata = data.metadata;
7475 if(data.target !== undefined) self.target = data.target;
76 if(data.host_os !== undefined) self.host_os = data.host_os;
7577 if(data.hostnames !== undefined) self.hostnames = data.hostnames;
7678 if(data.service !== undefined) self.service = data.service;
7779 if(data.owner !== undefined) self.owner = data.owner;
189189 $scope.new = function(){
190190 $scope.modal = $uibModal.open({
191191 templateUrl: 'scripts/workspaces/partials/modalNew.html',
192 backdrop : 'static',
192193 controller: 'workspacesModalNew',
193194 size: 'lg'
194195 });
217218 var oldName = workspace.name;
218219 var modal = $uibModal.open({
219220 templateUrl: 'scripts/workspaces/partials/modalEdit.html',
221 backdrop : 'static',
220222 controller: 'workspacesModalEdit',
221223 size: 'lg',
222224 resolve: {
1616 <ng-form name="form" novalidate>
1717 <label class="sr-only" for="wsp-name">Workspace Name</label>
1818 <input type="text" class="form-control"
19 ng-pattern=/^[a-z][a-z0-9\_\$\(\)\+\-\/]*$/ id="vuln-name" name="name" placeholder="Workspace Name"
19 ng-pattern=/^[a-z0-9][a-z0-9\_\$\(\)\+\-\/]*$/ id="vuln-name" name="name" placeholder="Workspace Name"
2020 ng-model="workspace.name" required/>
2121 <div ng-if="form.$invalid">
2222 <div class="alert alert-danger target_not_selected" role="alert" ng-hide="">
2323 <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
2424 <span class="sr-only">Error:</span>
2525 <button type="button" class="close" data-dismiss="alert"><span area-hidden="true">&times;</span><span class="sr-only">Close</span></button>
26 Workspace name should follow pattern [a-z][a-z0-9_$()+-/]*
26 Workspace name should follow pattern [a-z0-9][a-z0-9_$()+-/]*
2727 </div>
2828 </div>
2929 </ng-form>
1616 <ng-form name="form" novalidate>
1717 <label class="sr-only" for="wsp-name">Workspace Name</label>
1818 <input type="text" class="form-control"
19 ng-pattern=/^[a-z][a-z0-9\_\$\(\)\+\-\/]*$/ id="vuln-name" name="name" placeholder="Workspace Name"
19 ng-pattern=/^[a-z0-9][a-z0-9\_\$\(\)\+\-\/]*$/ id="vuln-name" name="name" placeholder="Workspace Name"
2020 ng-model="workspace.name" required/>
2121 <div ng-if="form.$invalid">
2222 <div class="alert alert-danger target_not_selected" role="alert" ng-hide="">
2323 <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
2424 <span class="sr-only">Error:</span>
2525 <button type="button" class="close" data-dismiss="alert"><span area-hidden="true">&times;</span><span class="sr-only">Close</span></button>
26 Workspace name should follow pattern [a-z][a-z0-9_$()+-/]*
26 Workspace name should follow pattern [a-z0-9][a-z0-9_$()+-/]*
2727 </div>
2828 </div>
2929 </ng-form>
131131
132132 .table-v3 .ui-grid-header .hosts-list-checkall{
133133 position: relative;
134 top: 3px;
135 left: 7px;
134 text-align: center;
136135 }
44
55 '''
66 import random
7 import string
78 import factory
89 import datetime
910 import unicodedata
8283
8384 class WorkspaceFactory(FaradayFactory):
8485
85 name = FuzzyText()
86 name = FuzzyText(chars=string.lowercase+string.digits)
8687 creator = factory.SubFactory(UserFactory)
8788
8889 class Meta:
7676 def test_vuln_count(workspace, second_workspace):
7777 populate_workspace(workspace)
7878 populate_workspace(second_workspace)
79 workspace = Workspace.query_with_count(False).filter(
79 workspace = Workspace.query_with_count(None).filter(
8080 Workspace.id == workspace.id).first()
8181 assert workspace.vulnerability_web_count == WEB_VULN_COUNT
8282 assert workspace.vulnerability_code_count == SOURCE_CODE_VULN_COUNT
10151015 assert res.status_code == 200
10161016 for v in res.json['data']:
10171017 assert v['target'] == host.ip
1018
1019 @pytest.mark.usefixtures('mock_envelope_list')
1020 def test_os(self, test_client, session, second_workspace,
1021 host_factory, service_factory,
1022 vulnerability_factory, vulnerability_web_factory):
1023 host_factory.create_batch(5, workspace=second_workspace)
1024 service_factory.create_batch(5, workspace=second_workspace)
1025 host = host_factory.create(workspace=second_workspace)
1026 service = service_factory.create(host=host,
1027 workspace=second_workspace)
1028 vulns = [
1029 vulnerability_factory.create(host=host, service=None,
1030 workspace=second_workspace),
1031 vulnerability_factory.create(service=service, host=None,
1032 workspace=second_workspace),
1033 vulnerability_web_factory.create(service=service,
1034 workspace=second_workspace),
1035 ]
1036
1037 session.commit()
1038 res = test_client.get(self.url(workspace=second_workspace))
1039 assert res.status_code == 200
1040 for v in res.json['data']:
1041 assert v['host_os'] == host.os
10181042
10191043 @pytest.mark.usefixtures('mock_envelope_list')
10201044 def test_filter_by_command_id(self, test_client, session,
2626 assert res.status_code == 200
2727 assert res.json['stats']['hosts'] == 1
2828
29 @pytest.mark.parametrize('querystring', [
30 '',
31 '?confirmed=0',
32 '?confirmed=false'
33 ])
3429
3530 def test_vuln_count(self,
3631 vulnerability_factory,
3732 test_client,
38 session,
39 querystring):
33 session):
4034 vulns = vulnerability_factory.create_batch(8, workspace=self.first_object,
41 confirmed=False)
35 confirmed=False)
4236 vulns += vulnerability_factory.create_batch(5, workspace=self.first_object,
43 confirmed=True)
44 session.add_all(vulns)
45 session.commit()
46 res = test_client.get(self.url(self.first_object) + querystring)
37 confirmed=True)
38 session.add_all(vulns)
39 session.commit()
40 res = test_client.get(self.url(self.first_object))
4741 assert res.status_code == 200
4842 assert res.json['stats']['total_vulns'] == 13
4943
5852 session,
5953 querystring):
6054 vulns = vulnerability_factory.create_batch(8, workspace=self.first_object,
61 confirmed=False)
55 confirmed=False)
6256 vulns += vulnerability_factory.create_batch(5, workspace=self.first_object,
63 confirmed=True)
57 confirmed=True)
6458 session.add_all(vulns)
6559 session.commit()
6660 res = test_client.get(self.url(self.first_object) + querystring)
6761 assert res.status_code == 200
6862 assert res.json['stats']['total_vulns'] == 5
63
64 @pytest.mark.parametrize('querystring', [
65 '?confirmed=0',
66 '?confirmed=false'
67 ])
68
69 def test_vuln_count_confirmed(self,
70 vulnerability_factory,
71 test_client,
72 session,
73 querystring):
74 vulns = vulnerability_factory.create_batch(8, workspace=self.first_object,
75 confirmed=False)
76 vulns += vulnerability_factory.create_batch(5, workspace=self.first_object,
77 confirmed=True)
78 session.add_all(vulns)
79 session.commit()
80 res = test_client.get(self.url(self.first_object) + querystring)
81 assert res.status_code == 200
82 assert res.json['stats']['total_vulns'] == 8
6983
7084 def test_create_fails_with_valid_duration(self, session, test_client):
7185 workspace_count_previous = session.query(Workspace).count()
7892 assert workspace_count_previous + 1 == session.query(Workspace).count()
7993 assert res.json['duration']['start_date'] == start_date
8094 assert res.json['duration']['end_date'] == end_date
95
96 def test_create_fails_with_mayus(self, session, test_client):
97 workspace_count_previous = session.query(Workspace).count()
98 raw_data = {'name': 'sWtr'}
99 res = test_client.post('/v2/ws/', data=raw_data)
100 assert res.status_code == 400
101 assert workspace_count_previous == session.query(Workspace).count()
102
103 def test_create_fails_with_special_character(self, session, test_client):
104 workspace_count_previous = session.query(Workspace).count()
105 raw_data = {'name': '$wtr'}
106 res = test_client.post('/v2/ws/', data=raw_data)
107 assert res.status_code == 400
108 assert workspace_count_previous == session.query(Workspace).count()
109
110 def test_create_with_initial_number(self, session, test_client):
111 workspace_count_previous = session.query(Workspace).count()
112 raw_data = {'name': '2$wtr'}
113 res = test_client.post('/v2/ws/', data=raw_data)
114 assert res.status_code == 201
115 assert workspace_count_previous + 1 == session.query(Workspace).count()
81116
82117 def test_create_fails_with_invalid_duration_start_type(self,
83118 session,
0 import unittest
1
2
3 class ImportTests(unittest.TestCase):
4
5 def test_couchdb(self):
6 from server.config import couchdb
7 self.host = couchdb.host
8 self.password = couchdb.password
9 self.protocol = couchdb.protocol
10 self.port = couchdb.port
11 self.ssl_port = couchdb.ssl_port
12 self.user = couchdb.user
13
14 def test_database(self):
15 from server.config import database
16 self.connection_string = database.connection_string
17
18 def test_faraday_server(self):
19 from server.config import faraday_server
20 self.bind_address = faraday_server.bind_address
21 self.port = faraday_server.port
22 self.secret_key = faraday_server.secret_key
23 self.websocket_port = faraday_server.websocket_port
24
25 def test_ldap(self):
26 from server.config import ldap
27 self.admin_group = ldap.admin_group
28 self.client_group = ldap.client_group
29 self.disconnect_timeout = ldap.disconnect_timeout
30 self.domain_dn = ldap.domain_dn
31 self.enabled = ldap.enabled
32 self.pentester_group = ldap.pentester_group
33 self.port = ldap.port
34 self.server = ldap.server
35 self.use_ldaps = ldap.use_ldaps
36 self.use_start_tls = ldap.use_start_tls
37
38 def test_ssl(self):
39 from server.config import ssl
40 self.certificate = ssl.certificate
41 self.keyfile = ssl.keyfile
42 self.port = ssl.port
43
44 def test_storage(self):
45 from server.config import storage
46 self.path = storage.path
47