1 | define(["dojo", "dojox", "dojo/data/util/filter", "dojo/data/util/simpleFetch", "dojo/date/stamp"], function(dojo, dojox) { |
---|
2 | dojo.experimental("dojox.data.AtomReadStore"); |
---|
3 | |
---|
4 | dojo.declare("dojox.data.AtomReadStore", null, { |
---|
5 | // summary: |
---|
6 | // A read only data store for Atom XML based services or documents |
---|
7 | // description: |
---|
8 | // A data store for Atom XML based services or documents. This store is still under development |
---|
9 | // and doesn't support wildcard filtering yet. Attribute filtering is limited to category or id. |
---|
10 | |
---|
11 | constructor: function(/* object */ args){ |
---|
12 | // summary: |
---|
13 | // Constructor for the AtomRead store. |
---|
14 | // args: |
---|
15 | // An anonymous object to initialize properties. It expects the following values: |
---|
16 | // url: The url to a service or an XML document that represents the store |
---|
17 | // unescapeHTML: A boolean to specify whether or not to unescape HTML text |
---|
18 | // sendQuery: A boolean indicate to add a query string to the service URL |
---|
19 | |
---|
20 | if(args){ |
---|
21 | this.url = args.url; |
---|
22 | this.rewriteUrl = args.rewriteUrl; |
---|
23 | this.label = args.label || this.label; |
---|
24 | this.sendQuery = (args.sendQuery || args.sendquery || this.sendQuery); |
---|
25 | this.unescapeHTML = args.unescapeHTML; |
---|
26 | if("urlPreventCache" in args){ |
---|
27 | this.urlPreventCache = args.urlPreventCache?true:false; |
---|
28 | } |
---|
29 | } |
---|
30 | if(!this.url){ |
---|
31 | throw new Error("AtomReadStore: a URL must be specified when creating the data store"); |
---|
32 | } |
---|
33 | }, |
---|
34 | |
---|
35 | //Values that may be set by the parser. |
---|
36 | //Ergo, have to be instantiated to something |
---|
37 | //So the parser knows how to set them. |
---|
38 | url: "", |
---|
39 | |
---|
40 | label: "title", |
---|
41 | |
---|
42 | sendQuery: false, |
---|
43 | |
---|
44 | unescapeHTML: false, |
---|
45 | |
---|
46 | //Configurable preventCache option for the URL. |
---|
47 | urlPreventCache: false, |
---|
48 | |
---|
49 | /* dojo.data.api.Read */ |
---|
50 | |
---|
51 | getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){ |
---|
52 | // summary: |
---|
53 | // Return an attribute value |
---|
54 | // description: |
---|
55 | // 'item' must be an instance of an object created by the AtomReadStore instance. |
---|
56 | // Accepted attributes are id, subtitle, title, summary, content, author, updated, |
---|
57 | // published, category, link and alternate |
---|
58 | // item: |
---|
59 | // An item returned by a call to the 'fetch' method. |
---|
60 | // attribute: |
---|
61 | // A attribute of the Atom Entry |
---|
62 | // defaultValue: |
---|
63 | // A default value |
---|
64 | // returns: |
---|
65 | // An attribute value found, otherwise 'defaultValue' |
---|
66 | this._assertIsItem(item); |
---|
67 | this._assertIsAttribute(attribute); |
---|
68 | this._initItem(item); |
---|
69 | attribute = attribute.toLowerCase(); |
---|
70 | //If the attribute has previously been retrieved, then return it |
---|
71 | if(!item._attribs[attribute] && !item._parsed){ |
---|
72 | this._parseItem(item); |
---|
73 | item._parsed = true; |
---|
74 | } |
---|
75 | var retVal = item._attribs[attribute]; |
---|
76 | |
---|
77 | if(!retVal && attribute == "summary"){ |
---|
78 | var content = this.getValue(item, "content"); |
---|
79 | var regexp = new RegExp("/(<([^>]+)>)/g", "i"); |
---|
80 | var text = content.text.replace(regexp,""); |
---|
81 | retVal = { |
---|
82 | text: text.substring(0, Math.min(400, text.length)), |
---|
83 | type: "text" |
---|
84 | }; |
---|
85 | item._attribs[attribute] = retVal; |
---|
86 | } |
---|
87 | |
---|
88 | if(retVal && this.unescapeHTML){ |
---|
89 | if((attribute == "content" || attribute == "summary" || attribute == "subtitle") && !item["_"+attribute+"Escaped"]){ |
---|
90 | retVal.text = this._unescapeHTML(retVal.text); |
---|
91 | item["_"+attribute+"Escaped"] = true; |
---|
92 | } |
---|
93 | } |
---|
94 | return retVal ? dojo.isArray(retVal) ? retVal[0]: retVal : defaultValue; |
---|
95 | }, |
---|
96 | |
---|
97 | getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){ |
---|
98 | // summary: |
---|
99 | // Return an attribute value |
---|
100 | // description: |
---|
101 | // 'item' must be an instance of an object created by the AtomReadStore instance. |
---|
102 | // Accepted attributes are id, subtitle, title, summary, content, author, updated, |
---|
103 | // published, category, link and alternate |
---|
104 | // item: |
---|
105 | // An item returned by a call to the 'fetch' method. |
---|
106 | // attribute: |
---|
107 | // A attribute of the Atom Entry |
---|
108 | // returns: |
---|
109 | // An array of values for the attribute value found, otherwise 'defaultValue' |
---|
110 | this._assertIsItem(item); |
---|
111 | this._assertIsAttribute(attribute); |
---|
112 | this._initItem(item); |
---|
113 | attribute = attribute.toLowerCase(); |
---|
114 | //If the attribute has previously been retrieved, then return it |
---|
115 | if(!item._attribs[attribute]){ |
---|
116 | this._parseItem(item); |
---|
117 | } |
---|
118 | var retVal = item._attribs[attribute]; |
---|
119 | return retVal ? ((retVal.length !== undefined && typeof(retVal) !== "string") ? retVal : [retVal]) : undefined; |
---|
120 | }, |
---|
121 | |
---|
122 | getAttributes: function(/* item */ item){ |
---|
123 | // summary: |
---|
124 | // Return an array of attribute names |
---|
125 | // description: |
---|
126 | // 'item' must be have been created by the AtomReadStore instance. |
---|
127 | // tag names of child elements and XML attribute names of attributes |
---|
128 | // specified to the element are returned along with special attribute |
---|
129 | // names applicable to the element including "tagName", "childNodes" |
---|
130 | // if the element has child elements, "text()" if the element has |
---|
131 | // child text nodes, and attribute names in '_attributeMap' that match |
---|
132 | // the tag name of the element. |
---|
133 | // item: |
---|
134 | // An XML element |
---|
135 | // returns: |
---|
136 | // An array of attributes found |
---|
137 | this._assertIsItem(item); |
---|
138 | if(!item._attribs){ |
---|
139 | this._initItem(item); |
---|
140 | this._parseItem(item); |
---|
141 | } |
---|
142 | var attrNames = []; |
---|
143 | for(var x in item._attribs){ |
---|
144 | attrNames.push(x); |
---|
145 | } |
---|
146 | return attrNames; //array |
---|
147 | }, |
---|
148 | |
---|
149 | hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){ |
---|
150 | // summary: |
---|
151 | // Check whether an element has the attribute |
---|
152 | // item: |
---|
153 | // 'item' must be created by the AtomReadStore instance. |
---|
154 | // attribute: |
---|
155 | // An attribute of an Atom Entry item. |
---|
156 | // returns: |
---|
157 | // True if the element has the attribute, otherwise false |
---|
158 | return (this.getValue(item, attribute) !== undefined); //boolean |
---|
159 | }, |
---|
160 | |
---|
161 | containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){ |
---|
162 | // summary: |
---|
163 | // Check whether the attribute values contain the value |
---|
164 | // item: |
---|
165 | // 'item' must be an instance of a dojox.data.XmlItem from the store instance. |
---|
166 | // attribute: |
---|
167 | // A tag name of a child element, An XML attribute name or one of |
---|
168 | // special names |
---|
169 | // returns: |
---|
170 | // True if the attribute values contain the value, otherwise false |
---|
171 | var values = this.getValues(item, attribute); |
---|
172 | for(var i = 0; i < values.length; i++){ |
---|
173 | if((typeof value === "string")){ |
---|
174 | if(values[i].toString && values[i].toString() === value){ |
---|
175 | return true; |
---|
176 | } |
---|
177 | }else if(values[i] === value){ |
---|
178 | return true; //boolean |
---|
179 | } |
---|
180 | } |
---|
181 | return false;//boolean |
---|
182 | }, |
---|
183 | |
---|
184 | isItem: function(/* anything */ something){ |
---|
185 | // summary: |
---|
186 | // Check whether the object is an item (XML element) |
---|
187 | // item: |
---|
188 | // An object to check |
---|
189 | // returns: |
---|
190 | // True if the object is an XML element, otherwise false |
---|
191 | if(something && something.element && something.store && something.store === this){ |
---|
192 | return true; //boolean |
---|
193 | } |
---|
194 | return false; //boolran |
---|
195 | }, |
---|
196 | |
---|
197 | isItemLoaded: function(/* anything */ something){ |
---|
198 | // summary: |
---|
199 | // Check whether the object is an item (XML element) and loaded |
---|
200 | // item: |
---|
201 | // An object to check |
---|
202 | // returns: |
---|
203 | // True if the object is an XML element, otherwise false |
---|
204 | return this.isItem(something); //boolean |
---|
205 | }, |
---|
206 | |
---|
207 | loadItem: function(/* object */ keywordArgs){ |
---|
208 | // summary: |
---|
209 | // Load an item (XML element) |
---|
210 | // keywordArgs: |
---|
211 | // object containing the args for loadItem. See dojo.data.api.Read.loadItem() |
---|
212 | }, |
---|
213 | |
---|
214 | getFeatures: function(){ |
---|
215 | // summary: |
---|
216 | // Return supported data APIs |
---|
217 | // returns: |
---|
218 | // "dojo.data.api.Read" and "dojo.data.api.Write" |
---|
219 | var features = { |
---|
220 | "dojo.data.api.Read": true |
---|
221 | }; |
---|
222 | return features; //array |
---|
223 | }, |
---|
224 | |
---|
225 | getLabel: function(/* item */ item){ |
---|
226 | // summary: |
---|
227 | // See dojo.data.api.Read.getLabel() |
---|
228 | if((this.label !== "") && this.isItem(item)){ |
---|
229 | var label = this.getValue(item,this.label); |
---|
230 | if(label && label.text){ |
---|
231 | return label.text; |
---|
232 | }else if(label){ |
---|
233 | return label.toString(); |
---|
234 | }else{ |
---|
235 | return undefined; |
---|
236 | } |
---|
237 | } |
---|
238 | return undefined; //undefined |
---|
239 | }, |
---|
240 | |
---|
241 | getLabelAttributes: function(/* item */ item){ |
---|
242 | // summary: |
---|
243 | // See dojo.data.api.Read.getLabelAttributes() |
---|
244 | if(this.label !== ""){ |
---|
245 | return [this.label]; //array |
---|
246 | } |
---|
247 | return null; //null |
---|
248 | }, |
---|
249 | |
---|
250 | getFeedValue: function(attribute, defaultValue){ |
---|
251 | // summary: |
---|
252 | // Non-API method for retrieving values regarding the Atom feed, |
---|
253 | // rather than the Atom entries. |
---|
254 | var values = this.getFeedValues(attribute, defaultValue); |
---|
255 | if(dojo.isArray(values)){ |
---|
256 | return values[0]; |
---|
257 | } |
---|
258 | return values; |
---|
259 | }, |
---|
260 | |
---|
261 | getFeedValues: function(attribute, defaultValue){ |
---|
262 | // summary: |
---|
263 | // Non-API method for retrieving values regarding the Atom feed, |
---|
264 | // rather than the Atom entries. |
---|
265 | if(!this.doc){ |
---|
266 | return defaultValue; |
---|
267 | } |
---|
268 | if(!this._feedMetaData){ |
---|
269 | this._feedMetaData = { |
---|
270 | element: this.doc.getElementsByTagName("feed")[0], |
---|
271 | store: this, |
---|
272 | _attribs: {} |
---|
273 | }; |
---|
274 | this._parseItem(this._feedMetaData); |
---|
275 | } |
---|
276 | return this._feedMetaData._attribs[attribute] || defaultValue; |
---|
277 | }, |
---|
278 | |
---|
279 | _initItem: function(item){ |
---|
280 | // summary: |
---|
281 | // Initializes an item before it can be parsed. |
---|
282 | if(!item._attribs){ |
---|
283 | item._attribs = {}; |
---|
284 | } |
---|
285 | }, |
---|
286 | |
---|
287 | _fetchItems: function(request, fetchHandler, errorHandler){ |
---|
288 | // summary: |
---|
289 | // Retrieves the items from the Atom XML document. |
---|
290 | var url = this._getFetchUrl(request); |
---|
291 | if(!url){ |
---|
292 | errorHandler(new Error("No URL specified.")); |
---|
293 | return; |
---|
294 | } |
---|
295 | var localRequest = (!this.sendQuery ? request : null); // use request for _getItems() |
---|
296 | |
---|
297 | var _this = this; |
---|
298 | var docHandler = function(data){ |
---|
299 | _this.doc = data; |
---|
300 | var items = _this._getItems(data, localRequest); |
---|
301 | var query = request.query; |
---|
302 | if(query){ |
---|
303 | if(query.id){ |
---|
304 | items = dojo.filter(items, function(item){ |
---|
305 | return (_this.getValue(item, "id") == query.id); |
---|
306 | }); |
---|
307 | }else if(query.category){ |
---|
308 | items = dojo.filter(items, function(entry){ |
---|
309 | var cats = _this.getValues(entry, "category"); |
---|
310 | if(!cats){ |
---|
311 | return false; |
---|
312 | } |
---|
313 | return dojo.some(cats, "return item.term=='"+query.category+"'"); |
---|
314 | }); |
---|
315 | } |
---|
316 | } |
---|
317 | |
---|
318 | if(items && items.length > 0){ |
---|
319 | fetchHandler(items, request); |
---|
320 | }else{ |
---|
321 | fetchHandler([], request); |
---|
322 | } |
---|
323 | }; |
---|
324 | |
---|
325 | if(this.doc){ |
---|
326 | docHandler(this.doc); |
---|
327 | }else{ |
---|
328 | var getArgs = { |
---|
329 | url: url, |
---|
330 | handleAs: "xml", |
---|
331 | preventCache: this.urlPreventCache |
---|
332 | }; |
---|
333 | var getHandler = dojo.xhrGet(getArgs); |
---|
334 | getHandler.addCallback(docHandler); |
---|
335 | |
---|
336 | getHandler.addErrback(function(data){ |
---|
337 | errorHandler(data, request); |
---|
338 | }); |
---|
339 | } |
---|
340 | }, |
---|
341 | |
---|
342 | _getFetchUrl: function(request){ |
---|
343 | if(!this.sendQuery){ |
---|
344 | return this.url; |
---|
345 | } |
---|
346 | var query = request.query; |
---|
347 | if(!query){ |
---|
348 | return this.url; |
---|
349 | } |
---|
350 | if(dojo.isString(query)){ |
---|
351 | return this.url + query; |
---|
352 | } |
---|
353 | var queryString = ""; |
---|
354 | for(var name in query){ |
---|
355 | var value = query[name]; |
---|
356 | if(value){ |
---|
357 | if(queryString){ |
---|
358 | queryString += "&"; |
---|
359 | } |
---|
360 | queryString += (name + "=" + value); |
---|
361 | } |
---|
362 | } |
---|
363 | if(!queryString){ |
---|
364 | return this.url; |
---|
365 | } |
---|
366 | //Check to see if the URL already has query params or not. |
---|
367 | var fullUrl = this.url; |
---|
368 | if(fullUrl.indexOf("?") < 0){ |
---|
369 | fullUrl += "?"; |
---|
370 | }else{ |
---|
371 | fullUrl += "&"; |
---|
372 | } |
---|
373 | return fullUrl + queryString; |
---|
374 | }, |
---|
375 | |
---|
376 | _getItems: function(document, request){ |
---|
377 | // summary: |
---|
378 | // Parses the document in a first pass |
---|
379 | if(this._items){ |
---|
380 | return this._items; |
---|
381 | } |
---|
382 | var items = []; |
---|
383 | var nodes = []; |
---|
384 | |
---|
385 | if(document.childNodes.length < 1){ |
---|
386 | this._items = items; |
---|
387 | console.log("dojox.data.AtomReadStore: Received an invalid Atom document. Check the content type header"); |
---|
388 | return items; |
---|
389 | } |
---|
390 | |
---|
391 | var feedNodes = dojo.filter(document.childNodes, "return item.tagName && item.tagName.toLowerCase() == 'feed'"); |
---|
392 | |
---|
393 | var query = request.query; |
---|
394 | |
---|
395 | if(!feedNodes || feedNodes.length != 1){ |
---|
396 | console.log("dojox.data.AtomReadStore: Received an invalid Atom document, number of feed tags = " + (feedNodes? feedNodes.length : 0)); |
---|
397 | return items; |
---|
398 | } |
---|
399 | |
---|
400 | nodes = dojo.filter(feedNodes[0].childNodes, "return item.tagName && item.tagName.toLowerCase() == 'entry'"); |
---|
401 | |
---|
402 | if(request.onBegin){ |
---|
403 | request.onBegin(nodes.length, this.sendQuery ? request : {}); |
---|
404 | } |
---|
405 | |
---|
406 | for(var i = 0; i < nodes.length; i++){ |
---|
407 | var node = nodes[i]; |
---|
408 | if(node.nodeType != 1 /*ELEMENT_NODE*/){ |
---|
409 | continue; |
---|
410 | } |
---|
411 | items.push(this._getItem(node)); |
---|
412 | } |
---|
413 | this._items = items; |
---|
414 | return items; |
---|
415 | }, |
---|
416 | |
---|
417 | close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ |
---|
418 | // summary: |
---|
419 | // See dojo.data.api.Read.close() |
---|
420 | }, |
---|
421 | |
---|
422 | /* internal API */ |
---|
423 | |
---|
424 | _getItem: function(element){ |
---|
425 | return { |
---|
426 | element: element, |
---|
427 | store: this |
---|
428 | }; |
---|
429 | }, |
---|
430 | |
---|
431 | _parseItem: function(item){ |
---|
432 | var attribs = item._attribs; |
---|
433 | var _this = this; |
---|
434 | var text, type; |
---|
435 | |
---|
436 | function getNodeText(node){ |
---|
437 | var txt = node.textContent || node.innerHTML || node.innerXML; |
---|
438 | if(!txt && node.childNodes[0]){ |
---|
439 | var child = node.childNodes[0]; |
---|
440 | if(child && (child.nodeType == 3 || child.nodeType == 4)){ |
---|
441 | txt = node.childNodes[0].nodeValue; |
---|
442 | } |
---|
443 | } |
---|
444 | return txt; |
---|
445 | } |
---|
446 | function parseTextAndType(node){ |
---|
447 | return {text: getNodeText(node),type: node.getAttribute("type")}; |
---|
448 | } |
---|
449 | dojo.forEach(item.element.childNodes, function(node){ |
---|
450 | var tagName = node.tagName ? node.tagName.toLowerCase() : ""; |
---|
451 | switch(tagName){ |
---|
452 | case "title": |
---|
453 | attribs[tagName] = { |
---|
454 | text: getNodeText(node), |
---|
455 | type: node.getAttribute("type") |
---|
456 | }; break; |
---|
457 | case "subtitle": |
---|
458 | case "summary": |
---|
459 | case "content": |
---|
460 | attribs[tagName] = parseTextAndType(node); |
---|
461 | break; |
---|
462 | case "author": |
---|
463 | var nameNode ,uriNode; |
---|
464 | dojo.forEach(node.childNodes, function(child){ |
---|
465 | if(!child.tagName){ |
---|
466 | return; |
---|
467 | } |
---|
468 | switch(child.tagName.toLowerCase()){ |
---|
469 | case "name": |
---|
470 | nameNode = child; |
---|
471 | break; |
---|
472 | case "uri": |
---|
473 | uriNode = child; |
---|
474 | break; |
---|
475 | } |
---|
476 | }); |
---|
477 | var author = {}; |
---|
478 | if(nameNode && nameNode.length == 1){ |
---|
479 | author.name = getNodeText(nameNode[0]); |
---|
480 | } |
---|
481 | if(uriNode && uriNode.length == 1){ |
---|
482 | author.uri = getNodeText(uriNode[0]); |
---|
483 | } |
---|
484 | attribs[tagName] = author; |
---|
485 | break; |
---|
486 | case "id": |
---|
487 | attribs[tagName] = getNodeText(node); |
---|
488 | break; |
---|
489 | case "updated": |
---|
490 | attribs[tagName] = dojo.date.stamp.fromISOString(getNodeText(node) ); |
---|
491 | break; |
---|
492 | case "published": |
---|
493 | attribs[tagName] = dojo.date.stamp.fromISOString(getNodeText(node)); |
---|
494 | break; |
---|
495 | case "category": |
---|
496 | if(!attribs[tagName]){ |
---|
497 | attribs[tagName] = []; |
---|
498 | } |
---|
499 | attribs[tagName].push({scheme:node.getAttribute("scheme"), term: node.getAttribute("term")}); |
---|
500 | break; |
---|
501 | case "link": |
---|
502 | if(!attribs[tagName]){ |
---|
503 | attribs[tagName] = []; |
---|
504 | } |
---|
505 | var link = { |
---|
506 | rel: node.getAttribute("rel"), |
---|
507 | href: node.getAttribute("href"), |
---|
508 | type: node.getAttribute("type")}; |
---|
509 | attribs[tagName].push(link); |
---|
510 | |
---|
511 | if(link.rel == "alternate"){ |
---|
512 | attribs["alternate"] = link; |
---|
513 | } |
---|
514 | break; |
---|
515 | default: |
---|
516 | break; |
---|
517 | } |
---|
518 | }); |
---|
519 | }, |
---|
520 | |
---|
521 | _unescapeHTML : function(text){ |
---|
522 | //Replace HTML character codes with their unencoded equivalents, e.g. ’ with ' |
---|
523 | text = text.replace(/’/m , "'").replace(/″/m , "\"").replace(/</m,">").replace(/>/m,"<").replace(/&/m,"&"); |
---|
524 | return text; |
---|
525 | }, |
---|
526 | |
---|
527 | _assertIsItem: function(/* item */ item){ |
---|
528 | // summary: |
---|
529 | // This function tests whether the item passed in is indeed an item in the store. |
---|
530 | // item: |
---|
531 | // The item to test for being contained by the store. |
---|
532 | if(!this.isItem(item)){ |
---|
533 | throw new Error("dojox.data.AtomReadStore: Invalid item argument."); |
---|
534 | } |
---|
535 | }, |
---|
536 | |
---|
537 | _assertIsAttribute: function(/* attribute-name-string */ attribute){ |
---|
538 | // summary: |
---|
539 | // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. |
---|
540 | // attribute: |
---|
541 | // The attribute to test for being contained by the store. |
---|
542 | if(typeof attribute !== "string"){ |
---|
543 | throw new Error("dojox.data.AtomReadStore: Invalid attribute argument."); |
---|
544 | } |
---|
545 | } |
---|
546 | }); |
---|
547 | dojo.extend(dojox.data.AtomReadStore,dojo.data.util.simpleFetch); |
---|
548 | |
---|
549 | return dojox.data.AtomReadStore; |
---|
550 | }); |
---|