PageRenderTime 64ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/ckan/public/scripts/vendor/ckanjs/1.0.0/ckanjs.js

https://bitbucket.org/okfn/ckan/
JavaScript | 1754 lines | 1369 code | 173 blank | 212 comment | 76 complexity | df3688ae905b70055f396ef0491ff010 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. this.CKAN = this.CKAN || {};
  2. this.CKAN.Client = (function (CKAN, $, _, Backbone, undefined) {
  3. // Client constructor. Creates a new client for communicating with
  4. // the CKAN API.
  5. function Client(config) {
  6. this._environment = {};
  7. this.configure(config || Client.defaults);
  8. _.bindAll(this, 'syncDataset', '_datasetConverter');
  9. }
  10. // Default config parameters for the Client.
  11. Client.defaults = {
  12. apiKey: '',
  13. endpoint: 'http://ckan.net'
  14. };
  15. // Extend the Client prototype with Backbone.Events to provide .bind(),
  16. // .unbind() and .trigger() methods.
  17. _.extend(Client.prototype, Backbone.Events, {
  18. cache: {
  19. dataset: new Backbone.Collection()
  20. },
  21. // Allows the implementor to specify an object literal of settings to
  22. // configure the current client. Options include:
  23. //
  24. // - apiKey: The API key for the current user to create/edit datasets.
  25. // - endpoint: The API endpoint to connect to.
  26. configure: function (config) {
  27. config = config || {};
  28. if (config.endpoint) {
  29. config.endpoint = config.endpoint.replace(/\/$/, '');
  30. config.restEndpoint = config.endpoint + '/api/2/rest';
  31. config.searchEndpoint = config.endpoint + '/api/2/search';
  32. }
  33. return this.environment(config);
  34. },
  35. // Client environment getter/setter. Environment variables can be retrieved
  36. // by providing a key string, if the key does not exist the method will
  37. // return `undefined`. To set keys either a key value pair can be provided
  38. // or an object literal containing multiple key/value pairs.
  39. environment: function (key, value) {
  40. if (typeof key === "string") {
  41. if (arguments.length === 1) {
  42. return this._environment[key];
  43. }
  44. this._environment[key] = value;
  45. } else {
  46. _.extend(this._environment, key);
  47. }
  48. return this;
  49. },
  50. // Helper method to fetch datasets from the server. Using this method to
  51. // fetch datasets will ensure that only one instance of a model per server
  52. // resource exists on the page at one time.
  53. //
  54. // The method accepts the dataset `"id"` and an object of `"options"`, these
  55. // can be any options accepted by the `.fetch()` method on `Backbone.Model`.
  56. // If the model already exists it will simply be returned otherwise an empty
  57. // model will be returned and the data requested from the server.
  58. //
  59. // var dataset = client.getDatasetById('my-data-id', {
  60. // success: function () {
  61. // // The model is now populated.
  62. // },
  63. // error: function (xhr) {
  64. // // Something went wrong check response status etc.
  65. // }
  66. // });
  67. //
  68. getDatasetById: function (id, options) {
  69. var cache = this.cache.dataset,
  70. dataset = cache.get(id);
  71. var ourOptions = options || {};
  72. if (!dataset) {
  73. dataset = this.createDataset({id: id});
  74. // Add the stub dataset to the global cache to ensure that only one
  75. // is ever created.
  76. cache.add(dataset);
  77. // Fetch the dataset from the server passing in any options provided.
  78. // Also set up a callback to remove the model from the cache in
  79. // case of error.
  80. ourOptions.error = function () {
  81. cache.remove(dataset);
  82. };
  83. // TODO: RP not sure i understand what this does and why it is needed
  84. dataset.fetch(ourOptions);
  85. }
  86. return dataset;
  87. },
  88. // Helper method to create a new instance of CKAN.Model.Dataset and
  89. // register a sync listener to update the representation on the server when
  90. // the model is created/updated/deleted.
  91. //
  92. // var myDataset = client.createDataset({
  93. // title: "My new data set"
  94. // });
  95. //
  96. // This ensures that the models are always saved with the latest environment
  97. // data.
  98. createDataset: function (attributes) {
  99. return (new CKAN.Model.Dataset(attributes)).bind('sync', this.syncDataset);
  100. },
  101. // A wrapper around Backbone.sync() that adds additional ajax options to
  102. // each request. These include the API key and the request url rather than
  103. // using the model to generate it.
  104. syncDataset: function (method, model, options) {
  105. // Get the package url.
  106. var url = this.environment('restEndpoint') + '/package';
  107. // Add additional request options.
  108. options = _.extend({}, {
  109. url: model.isNew() ? url : url + '/' + model.id,
  110. headers: {
  111. 'X-CKAN-API-KEY': this.environment('apiKey')
  112. }
  113. }, options);
  114. Backbone.sync(method, model, options);
  115. return this;
  116. },
  117. // Performs a search for datasets against the CKAN API. The `options`
  118. // argument can contain any keys supported by jQuery.ajax(). The query
  119. // parameters should be provided in the `options.query` property.
  120. //
  121. // var query = client.searchDatasets({
  122. // success: function (datasets) {
  123. // console.log(datasets); // Logs a Backbone.Collection
  124. // }
  125. // });
  126. //
  127. // The `options.success` method (and any other success callbacks) will
  128. // recieve a `SearchCollection` containing `Dataset` models. The method
  129. // returns a jqXHR object so that additional callbacks can be registered
  130. // with .success() and .error().
  131. searchDatasets: function (options) {
  132. options = options || {};
  133. options.data = _.defaults(options.query, {'limit': 10, 'all_fields': 1});
  134. delete options.query;
  135. return $.ajax(_.extend({
  136. url: this.environment('searchEndpoint') + '/package',
  137. converters: {
  138. 'text json': this._datasetConverter
  139. }
  140. }, options));
  141. },
  142. // A "converter" method for jQuery.ajax() this is used to convert the
  143. // results from a search API request into models which in turn will be
  144. // passed into any registered success callbacks. We do this here so that
  145. // _all_ registered success callbacks recieve the same data rather than
  146. // just the callback registered when the search was made.
  147. _datasetConverter: function (raw) {
  148. var json = $.parseJSON(raw),
  149. models = _.map(json.results, function (attributes) {
  150. return this.createDataset(attributes);
  151. }, this);
  152. return new CKAN.Model.SearchCollection(models, {total: json.count});
  153. },
  154. // Performs a query on CKAN API.
  155. // The `options` argument can contain any keys supported by jQuery.ajax().
  156. // In addition it should contain either a url or offset variable. If
  157. // offset provided it will be used to construct the full api url by
  158. // prepending the endpoint plus 'api' (i.e. offset of '/2/rest/package'
  159. // will become '{endpoint}/api/2/rest'.
  160. //
  161. // The `options.success` method (and any other success callbacks) will
  162. // recieve a `SearchCollection` containing `Dataset` models. The method
  163. // returns a jqXHR object so that additional callbacks can be registered
  164. // with .success() and .error().
  165. apiCall: function (options) {
  166. options = options || {};
  167. // Add additional request options.
  168. options = _.extend({}, {
  169. url: this.environment('endpoint') + '/api' + options.offset,
  170. headers: {
  171. 'X-CKAN-API-KEY': this.environment('apiKey')
  172. }
  173. }, options);
  174. return $.ajax(options);
  175. },
  176. // wrap CKAN /api/storage/auth/form - see http://packages.python.org/ckanext-storage
  177. // params and returns value are as for that API
  178. // key is file label/path
  179. getStorageAuthForm: function(key, options) {
  180. options.offset = '/storage/auth/form/' + key;
  181. this.apiCall(options);
  182. }
  183. });
  184. return Client;
  185. })(this.CKAN, this.$, this._, this.Backbone);
  186. this.CKAN = this.CKAN || {};
  187. // Global object that stores all CKAN models.
  188. CKAN.Model = function ($, _, Backbone, undefined) {
  189. var Model = {};
  190. // Simple validator helper returns a `validate()` function that checks
  191. // the provided model keys and returns an error object if these do not
  192. // exist on the model or the attributes object provided.\
  193. //
  194. // validate: validator('title', 'description', url)
  195. //
  196. function validator() {
  197. var required = arguments;
  198. return function (attrs) {
  199. var errors;
  200. if (attrs) {
  201. _.each(required, function (key) {
  202. if (!attrs[key] && !this.get(key)) {
  203. if (!errors) {
  204. errors = {};
  205. }
  206. errors[key] = 'The "' + key + '" is required';
  207. }
  208. }, this);
  209. }
  210. return errors;
  211. };
  212. }
  213. // A Base model that all CKAN models inherit from. Methods that should be
  214. // shared across all models should be defined here.
  215. Model.Base = Backbone.Model.extend({
  216. // Extend the default Backbone.Model constructor simply to provide a named
  217. // function. This improves debugging in consoles such as the Webkit inspector.
  218. constructor: function Base(attributes, options) {
  219. Backbone.Model.prototype.constructor.apply(this, arguments);
  220. },
  221. // Rather than letting the models connect to the server themselves we
  222. // leave this to the implementor to decide how models are saved. This allows
  223. // the API details such as API key and enpoints to change without having
  224. // to update the models. When `.save()` or `.destroy()` is called the
  225. // `"sync"` event will be published with the arguments provided to `.sync()`.
  226. //
  227. // var package = new Package({name: 'My Package Name'});
  228. // package.bind('sync', Backbone.sync);
  229. //
  230. // This method returns itself for chaining.
  231. sync: function () {
  232. return this.trigger.apply(this, ['sync'].concat(_.toArray(arguments)));
  233. },
  234. // Overrides the standard `toJSON()` method to serialise any nested
  235. // Backbone models and collections (or any other object that has a `toJSON()`
  236. // method).
  237. toJSON: function () {
  238. var obj = Backbone.Model.prototype.toJSON.apply(this, arguments);
  239. _.each(obj, function (value, key) {
  240. if (value && typeof value === 'object' && value.toJSON) {
  241. obj[key] = value.toJSON();
  242. }
  243. });
  244. return obj;
  245. }
  246. });
  247. // Model objects
  248. Model.Dataset = Model.Base.extend({
  249. constructor: function Dataset() {
  250. // Define an key/model mapping for child relationships. These will be
  251. // managed as a Backbone collection when setting/getting the key.
  252. this.children = {
  253. resources: Model.Resource,
  254. relationships: Model.Relationship
  255. };
  256. Model.Base.prototype.constructor.apply(this, arguments);
  257. },
  258. defaults: {
  259. title: '',
  260. name: '',
  261. notes: '',
  262. resources: [],
  263. tags: []
  264. },
  265. // Override the `set()` method on `Backbone.Model` to handle resources as
  266. // relationships. This will now manually update the `"resouces"` collection
  267. // (using `_updateResources()`) with any `Resource` models provided rather
  268. // than replacing the key.
  269. set: function (attributes, options) {
  270. var children, validated;
  271. // If not yet defined set the child collections. This will be done when
  272. // set is called for the first time in the constructor.
  273. this._createChildren();
  274. // Check to see if any child keys are present in the attributes and
  275. // remove them from the object. Then update them seperately after the
  276. // parent `set()` method has been called.
  277. _.each(this.children, function (Model, key) {
  278. if (attributes && attributes[key]) {
  279. if (!(attributes[key] instanceof Backbone.Collection)) {
  280. if (!children) {
  281. children = {};
  282. }
  283. children[key] = attributes[key];
  284. delete attributes[key];
  285. }
  286. }
  287. }, this);
  288. validated = Model.Base.prototype.set.call(this, attributes, options);
  289. // Ensure validation passed before updating child models.
  290. if (validated && children) {
  291. this._updateChildren(children);
  292. }
  293. return validated;
  294. },
  295. // Checks to see if our model instance has Backbone collections defined for
  296. // child keys. If they do not exist it creates them.
  297. _createChildren: function () {
  298. _.each(this.children, function (Model, key) {
  299. if (!this.get(key)) {
  300. var newColl = new Backbone.Collection();
  301. this.attributes[key] = newColl;
  302. newColl.model = Model;
  303. // bind change events so updating the children trigger change on Dataset
  304. var self = this;
  305. // TODO: do we want to do all or be more selective
  306. newColl.bind('all', function() {
  307. self.trigger('change');
  308. });
  309. }
  310. }, this);
  311. return this;
  312. },
  313. // Manages the one to many relationship between resources and the dataset.
  314. // Accepts an array of Resources (ideally model instances but will convert
  315. // object literals into resources for you). New models will be added to the
  316. // collection and existing ones updated. Any pre-existing models not found
  317. // in the new array will be removed.
  318. _updateChildren: function (children) {
  319. _.each(children, function (models, key) {
  320. var collection = this.get(key),
  321. ids = {};
  322. // Add/Update models.
  323. _.each(models, function (model) {
  324. var existing = collection.get(model.id),
  325. isLiteral = !(model instanceof this.children[key]);
  326. // Provide the dataset key if not already there and current model is
  327. // not a relationship.
  328. if (isLiteral && key !== 'relationships') {
  329. model.dataset = this;
  330. delete model.package_id;
  331. }
  332. if (!existing) {
  333. collection.add(model);
  334. }
  335. else if (existing && isLiteral) {
  336. existing.set(model);
  337. }
  338. ids[model.id] = 1;
  339. }, this);
  340. // Remove missing models.
  341. collection.remove(collection.select(function (model) {
  342. return !ids[model.id];
  343. }));
  344. }, this);
  345. return this;
  346. },
  347. // NOTE: Returns localised URL.
  348. toTemplateJSON: function () {
  349. var out = this.toJSON();
  350. var title = this.get('title');
  351. out.displaytitle = title ? title : 'No title ...';
  352. var notes = this.get('notes');
  353. // Don't use a global Showdown; CKAN doesn't need that library
  354. var showdown = new Showdown.converter();
  355. out.notesHtml = showdown.makeHtml(notes ? notes : '');
  356. out.snippet = this.makeSnippet(out.notesHtml);
  357. return out;
  358. },
  359. makeSnippet: function (notesHtml) {
  360. var out = $(notesHtml).text();
  361. if (out.length > 190) {
  362. out = out.slice(0, 190) + ' ...';
  363. }
  364. return out;
  365. }
  366. });
  367. // A model for working with resources. Each resource is _required_ to have a
  368. // parent `Dataset`. This must be provided under the `"dataset"` key when the
  369. // `Resource` is created. This is handled for you when creating resources
  370. // via the `Dataset` `set()` method.
  371. //
  372. // The `save()`, `fetch()` and `delete()` methods are mapped to the parent
  373. // dataset and can be used to update a Resource's metadata.
  374. //
  375. // var resource = new Model.Resource({
  376. // name: 'myresource.csv',
  377. // url: 'http://www.example.com/myresource.csv',
  378. // dataset: dataset
  379. // });
  380. //
  381. // // Updates the resource name on the server by saving the parent dataset
  382. // resource.set({name: 'Some new name'});
  383. //
  384. Model.Resource = Model.Base.extend({
  385. constructor: function Resource() {
  386. Model.Base.prototype.constructor.apply(this, arguments);
  387. },
  388. // Override the `save()` method to update the Resource with attributes then
  389. // call the parent dataset and save that. Any `options` provided will be
  390. // passed on to the dataset `save()` method.
  391. save: function (attrs, options) {
  392. var validated = this.set(attrs);
  393. if (validated) {
  394. return this.get('dataset').save({}, options);
  395. }
  396. return validated;
  397. },
  398. // Override the `fetch()` method to call `fetch()` on the parent dataset.
  399. fetch: function (options) {
  400. return this.get('dataset').fetch(options);
  401. },
  402. // Override the `fetch()` method to trigger the `"destroy"` event which
  403. // will remove it from any collections then save the parent dataset.
  404. destroy: function (options) {
  405. return this.trigger('destroy', this).get('dataset').save({}, options);
  406. },
  407. // Override the `toJSON()` method to set the `"package_id"` key required
  408. // by the server.
  409. toJSON: function () {
  410. // Call Backbone.Model rather than Base to break the circular reference.
  411. var obj = Backbone.Model.prototype.toJSON.apply(this, arguments);
  412. if (obj.dataset) {
  413. obj.package_id = obj.dataset.id;
  414. delete obj.dataset;
  415. } else {
  416. obj.package_id = null;
  417. }
  418. return obj;
  419. },
  420. toTemplateJSON: function() {
  421. var obj = Backbone.Model.prototype.toJSON.apply(this, arguments);
  422. obj.displaytitle = obj.description ? obj.description : 'No description ...';
  423. return obj;
  424. },
  425. // Validates the provided attributes. Returns an object literal of
  426. // attribute/error pairs if invalid, `undefined` otherwise.
  427. validate: validator('url')
  428. });
  429. // Helper function that returns a stub method that warns the devloper that
  430. // this method has not yet been implemented.
  431. function apiPlaceholder(method) {
  432. var console = window.console;
  433. return function () {
  434. if (console && console.warn) {
  435. console.warn('The method "' + method + '" has not yet been implemented');
  436. }
  437. return this;
  438. };
  439. }
  440. // A model for working with relationship objects. These are currently just the
  441. // realtionship objects returned by the server wrapped in a `Base` model
  442. // instance. Currently there is no save or delete functionality.
  443. Model.Relationship = Model.Base.extend({
  444. constructor: function Relationship() {
  445. Model.Base.prototype.constructor.apply(this, arguments);
  446. },
  447. // Add placeholder method that simply returns itself to all methods that
  448. // interact with the server. This will also log a warning message to the
  449. // developer into the console.
  450. save: apiPlaceholder('save'),
  451. fetch: apiPlaceholder('fetch'),
  452. destroy: apiPlaceholder('destroy'),
  453. // Validates the provided attributes. Returns an object literal of
  454. // attribute/error pairs if invalid, `undefined` otherwise.
  455. validate: validator('object', 'subject', 'type')
  456. });
  457. // Collection for managing results from the CKAN search API. An additional
  458. // `options.total` parameter can be provided on initialisation to
  459. // indicate how many models there are on the server in total. This can
  460. // then be accessed via the `total` property.
  461. Model.SearchCollection = Backbone.Collection.extend({
  462. constructor: function SearchCollection(models, options) {
  463. if (options) {
  464. this.total = options.total;
  465. }
  466. Backbone.Collection.prototype.constructor.apply(this, arguments);
  467. }
  468. });
  469. return Model;
  470. }(this.jQuery, this._, this.Backbone);
  471. var CKAN = CKAN || {};
  472. CKAN.Templates = {
  473. minorNavigationDataset: ' \
  474. <ul class="tabbed"> \
  475. <li><a href="#dataset/${dataset.id}/view">View</a></li> \
  476. <li><a href="#dataset/${dataset.id}/edit">Edit</a></li> \
  477. </ul> \
  478. '
  479. };
  480. var CKAN = CKAN || {};
  481. CKAN.View = function($) {
  482. var my = {};
  483. // Flash a notification message
  484. //
  485. // Parameters: msg, type. type is set as class on notification and should be one of success, error.
  486. // If type not defined defaults to success
  487. my.flash = function(msg, type) {
  488. if (type === undefined) {
  489. var type = 'success'
  490. }
  491. $.event.trigger('notification', [msg, type]);
  492. };
  493. my.NotificationView = Backbone.View.extend({
  494. initialize: function() {
  495. $.template('notificationTemplate',
  496. '<div class="flash-banner ${type}">${message} <button>X</button></div>');
  497. var self = this;
  498. $(document).bind('notification', function(e, msg, type) {
  499. self.render(msg, type)
  500. });
  501. },
  502. events: {
  503. 'click .flash-banner button': 'hide'
  504. },
  505. render: function(msg, type) {
  506. var _out = $.tmpl('notificationTemplate', {'message': msg, 'type': type})
  507. this.el.html(_out);
  508. this.el.slideDown(400);
  509. },
  510. hide: function() {
  511. this.el.slideUp(200);
  512. }
  513. });
  514. my.ConfigView = Backbone.View.extend({
  515. initialize: function() {
  516. this.cfg = {};
  517. this.$ckanUrl = this.el.find('input[name=ckan-url]');
  518. this.$apikey = this.el.find('input[name=ckan-api-key]');
  519. var cfg = this.options.config;
  520. this.$ckanUrl.val(cfg.endpoint);
  521. this.$apikey.val(cfg.apiKey);
  522. },
  523. events: {
  524. 'submit #config-form': 'updateConfig'
  525. },
  526. updateConfig: function(e) {
  527. e.preventDefault();
  528. this.saveConfig();
  529. CKAN.View.flash('Saved configuration');
  530. },
  531. saveConfig: function() {
  532. this.cfg = {
  533. 'endpoint': this.$ckanUrl.val(),
  534. 'apiKey': this.$apikey.val()
  535. };
  536. $.event.trigger('config:update', this.cfg);
  537. }
  538. });
  539. my.DatasetEditView = Backbone.View.extend({
  540. initialize: function() {
  541. _.bindAll(this, 'saveData', 'render');
  542. this.model.bind('change', this.render);
  543. },
  544. render: function() {
  545. tmplData = {
  546. dataset: this.model.toTemplateJSON()
  547. }
  548. var tmpl = $.tmpl(CKAN.Templates.datasetForm, tmplData);
  549. $(this.el).html(tmpl);
  550. if (tmplData.dataset.id) { // edit not add
  551. $('#minornavigation').html($.tmpl(CKAN.Templates.minorNavigationDataset, tmplData));
  552. }
  553. return this;
  554. },
  555. events: {
  556. 'submit form.dataset': 'saveData',
  557. 'click .previewable-textarea a': 'togglePreview',
  558. 'click .dataset-form-navigation a': 'showFormPart'
  559. },
  560. showFormPart: function(e) {
  561. e.preventDefault();
  562. var action = $(e.target)[0].href.split('#')[1];
  563. $('.dataset-form-navigation a').removeClass('selected');
  564. $('.dataset-form-navigation a[href=#' + action + ']').addClass('selected');
  565. },
  566. saveData: function(e) {
  567. e.preventDefault();
  568. this.model.set(this.getData());
  569. this.model.save({}, {
  570. success: function(model) {
  571. CKAN.View.flash('Saved dataset');
  572. window.location.hash = '#dataset/' + model.id + '/view';
  573. },
  574. error: function(model, error) {
  575. CKAN.View.flash('Error saving dataset ' + error.responseText, 'error');
  576. }
  577. });
  578. },
  579. getData: function() {
  580. var _data = $(this.el).find('form.dataset').serializeArray();
  581. modelData = {};
  582. $.each(_data, function(idx, value) {
  583. modelData[value.name.split('--')[1]] = value.value
  584. });
  585. return modelData;
  586. },
  587. togglePreview: function(e) {
  588. // set model data as we use it below for notesHtml
  589. this.model.set(this.getData());
  590. e.preventDefault();
  591. var el = $(e.target);
  592. var action = el.attr('action');
  593. var div = el.closest('.previewable-textarea');
  594. div.find('.tabs a').removeClass('selected');
  595. div.find('.tabs a[action='+action+']').addClass('selected');
  596. var textarea = div.find('textarea');
  597. var preview = div.find('.preview');
  598. if (action=='preview') {
  599. preview.html(this.model.toTemplateJSON().notesHtml);
  600. textarea.hide();
  601. preview.show();
  602. } else {
  603. textarea.show();
  604. preview.hide();
  605. }
  606. return false;
  607. }
  608. });
  609. my.DatasetFullView = Backbone.View.extend({
  610. initialize: function() {
  611. _.bindAll(this, 'render');
  612. this.model.bind('change', this.render);
  613. // slightly painful but we have to set this up here so
  614. // it has access to self because when called this will
  615. // be overridden and refer to the element in dom that
  616. // was being saved
  617. var self = this;
  618. this.saveFromEditable = function(value, settings) {
  619. var _attribute = $(this).attr('backbone-attribute');
  620. var _data = {};
  621. _data[_attribute] = value;
  622. self.model.set(_data);
  623. self.model.save({}, {
  624. success: function(model) {
  625. CKAN.View.flash('Saved updated notes');
  626. },
  627. error: function(model, error) {
  628. CKAN.View.flash('Error saving notes ' + error.responseText, 'error');
  629. }
  630. });
  631. // do not worry too much about what we return here
  632. // because update of model will automatically lead to
  633. // re-render
  634. return value;
  635. };
  636. },
  637. events: {
  638. 'click .action-add-resource': 'showResourceAdd'
  639. },
  640. render: function() {
  641. var tmplData = {
  642. domain: this.options.domain,
  643. dataset: this.model.toTemplateJSON(),
  644. };
  645. $('.page-heading').html(tmplData.dataset.displaytitle);
  646. $('#minornavigation').html($.tmpl(CKAN.Templates.minorNavigationDataset, tmplData));
  647. $('#sidebar .widget-list').html($.tmpl(CKAN.Templates.sidebarDatasetView, tmplData));
  648. this.el.html($.tmpl(CKAN.Templates.datasetView, tmplData));
  649. this.setupEditable();
  650. return this;
  651. },
  652. setupEditable: function() {
  653. var self = this;
  654. this.el.find('.editable-area').editable(
  655. self.saveFromEditable, {
  656. type : 'textarea',
  657. cancel : 'Cancel',
  658. submit : 'OK',
  659. tooltip : 'Click to edit...',
  660. onblur : 'ignore',
  661. data : function(value, settings) {
  662. var _attribute = $(this).attr('backbone-attribute');
  663. return self.model.get(_attribute);
  664. }
  665. }
  666. );
  667. },
  668. showResourceAdd: function(e) {
  669. var self = this;
  670. e.preventDefault();
  671. var $el = $('<div />').addClass('resource-add-dialog');
  672. $('body').append($el);
  673. var resource = new CKAN.Model.Resource({
  674. 'dataset': self.model
  675. });
  676. function handleNewResourceSave(model) {
  677. var res = self.model.get('resources');
  678. res.add(model);
  679. $el.dialog('close');
  680. self.model.save({}, {
  681. success: function(model) {
  682. CKAN.View.flash('Saved dataset');
  683. // TODO: no need to re-render (should happen automatically)
  684. self.render();
  685. }
  686. , error: function(model, error) {
  687. CKAN.View.flash('Failed to save: ' + error, 'error');
  688. }
  689. });
  690. }
  691. resource.bind('change', handleNewResourceSave);
  692. var resourceView = new CKAN.View.ResourceCreate({
  693. el: $el,
  694. model: resource
  695. });
  696. resourceView.render();
  697. dialogOptions = {
  698. autoOpen: false,
  699. // does not seem to work for width ...
  700. position: ['center', 'center'],
  701. buttons: [],
  702. width: 660,
  703. resize: 'auto',
  704. modal: false,
  705. draggable: true,
  706. resizable: true
  707. };
  708. dialogOptions.title = 'Add Data (File, API, ...)';
  709. $el.dialog(dialogOptions);
  710. $el.dialog('open');
  711. $el.bind("dialogbeforeclose", function () {
  712. self.el.find('.resource-add-dialog').remove();
  713. });
  714. }
  715. });
  716. my.DatasetSearchView = Backbone.View.extend({
  717. events: {
  718. 'submit #search-form': 'onSearch'
  719. },
  720. initialize: function(options) {
  721. var view = this;
  722. // Temporarily provide the view with access to the client for searching.
  723. this.client = options.client;
  724. this.$results = this.el.find('.results');
  725. this.$datasetList = this.$results.find('.datasets');
  726. this.$dialog = this.el.find('.dialog');
  727. this.resultView = new CKAN.View.DatasetListing({
  728. collection: new Backbone.Collection(),
  729. el: this.$datasetList
  730. });
  731. _.bindAll(this, "render");
  732. },
  733. render: function() {
  734. this.$('.count').html(this.totalResults);
  735. this.hideSpinner();
  736. this.$results.show();
  737. return this;
  738. },
  739. onSearch: function (event) {
  740. event.preventDefault();
  741. var q = $(this.el).find('input.search').val();
  742. this.doSearch(q);
  743. },
  744. doSearch: function (q) {
  745. $(this.el).find('input.search').val(q),
  746. self = this;
  747. this.showSpinner();
  748. this.$results.hide();
  749. this.$results.find('.datasets').empty();
  750. this.client.searchDatasets({
  751. query: {q:q},
  752. success: function (collection) {
  753. self.totalResults = collection.total;
  754. self.resultView.setCollection(collection);
  755. self.render();
  756. }
  757. });
  758. },
  759. showSpinner: function() {
  760. this.$dialog.empty();
  761. this.$dialog.html('<h2>Loading results...</h2><img src="http://assets.okfn.org/images/icons/ajaxload-circle.gif" />');
  762. this.$dialog.show();
  763. },
  764. hideSpinner: function() {
  765. this.$dialog.empty().hide();
  766. }
  767. });
  768. my.ResourceView = Backbone.View.extend({
  769. render: function() {
  770. var resourceData = this.model.toTemplateJSON();
  771. var resourceDetails = {};
  772. var exclude = [ 'resource_group_id',
  773. 'description',
  774. 'url',
  775. 'position',
  776. 'id',
  777. 'webstore',
  778. 'qa',
  779. 'dataset',
  780. 'displaytitle'
  781. ];
  782. $.each(resourceData, function(key, value) {
  783. if (! _.contains(exclude, key)) {
  784. resourceDetails[key] = value;
  785. }
  786. });
  787. tmplData = {
  788. dataset: this.model.get('dataset').toTemplateJSON(),
  789. resource: resourceData,
  790. resourceDetails: resourceDetails
  791. };
  792. $('.page-heading').html(tmplData.dataset.name + ' / ' + tmplData.resource.displaytitle);
  793. var tmpl = $.tmpl(CKAN.Templates.resourceView, tmplData);
  794. $(this.el).html(tmpl);
  795. return this;
  796. },
  797. events: {
  798. }
  799. });
  800. my.ResourceEditView = Backbone.View.extend({
  801. render: function() {
  802. var tmpl = $.tmpl(CKAN.Templates.resourceForm, this.model.toJSON());
  803. $(this.el).html(tmpl);
  804. return this;
  805. },
  806. events: {
  807. 'submit form': 'saveData'
  808. },
  809. saveData: function() {
  810. // only set rather than save as can only save resources as part of a dataset atm
  811. this.model.set(this.getData(), {
  812. error: function(model, error) {
  813. var msg = 'Failed to save, possibly due to invalid data ';
  814. msg += JSON.stringify(error);
  815. alert(msg);
  816. }
  817. });
  818. return false;
  819. },
  820. getData: function() {
  821. var _data = $(this.el).find('form.resource').serializeArray();
  822. modelData = {};
  823. $.each(_data, function(idx, value) {
  824. modelData[value.name.split('--')[1]] = value.value
  825. });
  826. return modelData;
  827. }
  828. });
  829. return my;
  830. }(jQuery);
  831. var CKAN = CKAN || {};
  832. CKAN.UI = function($) {
  833. var my = {};
  834. my.Workspace = Backbone.Router.extend({
  835. routes: {
  836. "": "index",
  837. "search": "search",
  838. "search/:query": "search",
  839. "search/:query/p:page": "search",
  840. "dataset/:id/view": "datasetView",
  841. "dataset/:id/edit": "datasetEdit",
  842. "dataset/:datasetId/resource/:resourceId": "resourceView",
  843. "add-dataset": "datasetAdd",
  844. "add-resource": "resourceAdd",
  845. "config": "config"
  846. },
  847. initialize: function(options) {
  848. var self = this;
  849. var defaultConfig = {
  850. endpoint: 'http://ckan.net',
  851. apiKey: ''
  852. };
  853. var config = options.config || defaultConfig;
  854. this.client = new CKAN.Client(config);
  855. if (options.fixtures && options.fixtures.datasets) {
  856. $.each(options.fixtures.datasets, function(idx, obj) {
  857. var collection = self.client.cache.dataset;
  858. collection.add(new CKAN.Model.Dataset(obj));
  859. });
  860. }
  861. var newPkg = this.client.createDataset();
  862. var newCreateView = new CKAN.View.DatasetEditView({model: newPkg, el: $('#dataset-add-page')});
  863. newCreateView.render();
  864. var newResource = new CKAN.Model.Resource({
  865. dataset: newPkg
  866. });
  867. var newResourceEditView = new CKAN.View.ResourceEditView({model: newResource, el: $('#add-resource-page')});
  868. newResourceEditView.render();
  869. var searchView = this.searchView = new CKAN.View.DatasetSearchView({
  870. client: this.client,
  871. el: $('#search-page')
  872. });
  873. // set up top bar search
  874. $('#menusearch').find('form').submit(function(e) {
  875. e.preventDefault();
  876. var _el = $(e.target);
  877. var _q = _el.find('input[name="q"]').val();
  878. searchView.doSearch(_q);
  879. self.search(_q);
  880. });
  881. var configView = new CKAN.View.ConfigView({
  882. el: $('#config-page'),
  883. config: config
  884. });
  885. $(document).bind('config:update', function(e, cfg) {
  886. self.client.configure(cfg);
  887. });
  888. this.notificationView = new CKAN.View.NotificationView({
  889. el: $('.flash-banner-box')
  890. });
  891. },
  892. switchView: function(view) {
  893. $('.page-view').hide();
  894. $('#sidebar .widget-list').empty();
  895. $('#minornavigation').empty();
  896. $('#' + view + '-page').show();
  897. },
  898. index: function(query, page) {
  899. this.search();
  900. },
  901. search: function(query, page) {
  902. this.switchView('search');
  903. $('.page-heading').html('Search');
  904. },
  905. _findDataset: function(id, callback) {
  906. var pkg = this.client.getDatasetById(id);
  907. if (pkg===undefined) {
  908. pkg = this.client.createDataset({id: id});
  909. pkg.fetch({
  910. success: callback,
  911. error: function() {
  912. alert('There was an error');
  913. }
  914. });
  915. } else {
  916. callback(pkg);
  917. }
  918. },
  919. datasetView: function(id) {
  920. var self = this;
  921. self.switchView('view');
  922. var $viewpage = $('#view-page');
  923. this._findDataset(id, function (model) {
  924. var newView = new CKAN.View.DatasetFullView({
  925. model: model,
  926. el: $viewpage
  927. });
  928. newView.render();
  929. });
  930. },
  931. datasetEdit: function(id) {
  932. this.switchView('dataset-edit');
  933. $('.page-heading').html('Edit Dataset');
  934. function _show(model) {
  935. var newView = new CKAN.View.DatasetEditView({model: model});
  936. $('#dataset-edit-page').html(newView.render().el);
  937. }
  938. this._findDataset(id, _show)
  939. },
  940. datasetAdd: function() {
  941. this.switchView('dataset-add');
  942. $('.page-heading').html('Add Dataset');
  943. $('#sidebar .widget-list').empty();
  944. },
  945. resourceView: function(datasetId, resourceId) {
  946. this.switchView('resource-view');
  947. var $viewpage = $('#resource-view-page');
  948. this._findDataset(datasetId, function (model) {
  949. var resource = model.get('resources').get(resourceId);
  950. var newView = new CKAN.View.ResourceView({
  951. model: resource,
  952. el: $viewpage
  953. });
  954. newView.render();
  955. });
  956. },
  957. resourceAdd: function() {
  958. this.switchView('add-resource');
  959. },
  960. config: function() {
  961. this.switchView('config');
  962. },
  963. url: function(controller, action, id) {
  964. if (id) {
  965. return '#' + controller + '/' + id + '/' + action;
  966. } else {
  967. return '#' + controller + '/' + action;
  968. }
  969. }
  970. });
  971. my.initialize = function(options) {
  972. my.workspace = new my.Workspace(options);
  973. Backbone.history.start()
  974. };
  975. return my;
  976. }(jQuery);
  977. CKAN.Templates.datasetForm = ' \
  978. <form class="dataset" action="" method="POST"> \
  979. <dl> \
  980. <dt> \
  981. <label class="field_opt" for="dataset--title"> \
  982. Title * \
  983. </label> \
  984. </dt> \
  985. <dd> \
  986. <input id="Dataset--title" name="Dataset--title" type="text" value="${dataset.title}" placeholder="A title (not a description) ..."/> \
  987. </dd> \
  988. \
  989. <dt> \
  990. <label class="field_req" for="Dataset--name"> \
  991. Name * \
  992. <span class="hints"> \
  993. A short unique name for the dataset - used in urls and restricted to [a-z] -_ \
  994. </span> \
  995. </label> \
  996. </dt> \
  997. <dd> \
  998. <input id="Dataset--name" maxlength="100" name="Dataset--name" type="text" value="${dataset.name}" placeholder="A shortish name usable in urls ..." /> \
  999. </dd> \
  1000. \
  1001. <dt> \
  1002. <label class="field_opt" for="Dataset--license_id"> \
  1003. Licence \
  1004. </label> \
  1005. </dt> \
  1006. <dd> \
  1007. <select id="Dataset--license_id" name="Dataset--license_id"> \
  1008. <option selected="selected" value=""></option> \
  1009. <option value="notspecified">Other::License Not Specified</option> \
  1010. </select> \
  1011. </dd> \
  1012. \
  1013. <dt> \
  1014. <label class="field_opt" for="Dataset--notes"> \
  1015. Description and Notes \
  1016. <span class="hints"> \
  1017. (You can use <a href="http://daringfireball.net/projects/markdown/syntax">Markdown formatting</a>) \
  1018. </span> \
  1019. </label> \
  1020. </dt> \
  1021. <dd> \
  1022. <div class="previewable-textarea"> \
  1023. <ul class="tabs"> \
  1024. <li><a href="#" action="write" class="selected">Write</a></li> \
  1025. <li><a href="#" action="preview">Preview</a></li> \
  1026. </ul> \
  1027. <textarea id="Dataset--notes" name="Dataset--notes" placeholder="Start with a summary sentence ...">${dataset.notes}</textarea> \
  1028. <div id="Dataset--notes-preview" class="preview" style="display: none;"> \
  1029. <div> \
  1030. </div> \
  1031. </dd> \
  1032. </dl> \
  1033. \
  1034. <div class="submit"> \
  1035. <input id="save" name="save" type="submit" value="Save" /> \
  1036. </div> \
  1037. </form> \
  1038. ';
  1039. CKAN.Templates.datasetFormSidebar = ' \
  1040. <div class="dataset-form-navigation"> \
  1041. <ul> \
  1042. <li> \
  1043. <a href="#basics" class="selected">Basics</a> \
  1044. </li> \
  1045. <li> \
  1046. <a href="#data">The Data</a> \
  1047. </li> \
  1048. <li> \
  1049. <a href="#additional"> \
  1050. Additional Information \
  1051. </a> \
  1052. </li> \
  1053. </ul> \
  1054. </div> \
  1055. ';
  1056. CKAN.Templates.datasetView = ' \
  1057. <div class="dataset view" dataset-id="${dataset.id}"> \
  1058. <div class="extract"> \
  1059. ${dataset.snippet} \
  1060. {{if dataset.snippet.length > 50}} \
  1061. <a href="#anchor-notes">Read more</a> \
  1062. {{/if}} \
  1063. </div> \
  1064. <div class="tags"> \
  1065. {{if dataset.tags.length}} \
  1066. <ul class="dataset-tags"> \
  1067. {{each dataset.tags}} \
  1068. <li>${$value}</li> \
  1069. {{/each}} \
  1070. </ul> \
  1071. {{/if}} \
  1072. </div> \
  1073. <div class="resources subsection"> \
  1074. <h3>Resources</h3> \
  1075. <table> \
  1076. <tr> \
  1077. <th>Description</th> \
  1078. <th>Format</th> \
  1079. <th>Actions</th> \
  1080. </tr> \
  1081. {{each dataset.resources}} \
  1082. <tr> \
  1083. <td> \
  1084. <a href="#dataset/${dataset.id}/resource/${$value.id}"> \
  1085. {{if $value.description}} \
  1086. ${$value.description} \
  1087. {{else}} \
  1088. (No description) \
  1089. {{/if}} \
  1090. </a> \
  1091. </td> \
  1092. <td>${$value.format}</td> \
  1093. <td><a href="${$value.url}" target="_blank" class="resource-download">Download</a> \
  1094. </tr> \
  1095. {{/each}} \
  1096. {{if !dataset.resources.length }} \
  1097. <tr><td>No resources.</td><td></td></tr> \
  1098. {{/if}} \
  1099. </table> \
  1100. <div class="add-resource"> \
  1101. <a href="#" class="action-add-resource">Add a resource</a> \
  1102. </div> \
  1103. </div> \
  1104. <div class="notes subsection"> \
  1105. <h3 id="anchor-notes">Notes</h3> \
  1106. <div class="notes-body editable-area" backbone-attribute="notes"> \
  1107. {{html dataset.notesHtml}} \
  1108. {{if !dataset.notes || dataset.notes.length === 0}} \
  1109. <em>No notes yet. Click to add some ...</em> \
  1110. {{/if}} \
  1111. </div> \
  1112. </div> \
  1113. <div class="details subsection"> \
  1114. <h3>Additional Information</h3> \
  1115. <table> \
  1116. <thead> \
  1117. <tr> \
  1118. <th>Field</th> \
  1119. <th>Value</th> \
  1120. </tr> \
  1121. </thead> \
  1122. <tbody> \
  1123. <tr> \
  1124. <td>Creator</td> \
  1125. <td>${dataset.author}</td> \
  1126. </tr> \
  1127. <tr> \
  1128. <td>Maintainer</td> \
  1129. <td>${dataset.maintainer}</td> \
  1130. </tr> \
  1131. {{each dataset.extras}} \
  1132. <tr> \
  1133. <td class="package-label" property="rdfs:label">${$index}</td> \
  1134. <td class="package-details" property="rdf:value">${$value}</td> \
  1135. </tr> \
  1136. {{/each}} \
  1137. </tbody> \
  1138. </table> \
  1139. </div> \
  1140. </div> \
  1141. ';
  1142. CKAN.Templates.sidebarDatasetView = ' \
  1143. <li class="widget-container widget_text"> \
  1144. <h3>Connections</h3> \
  1145. <ul> \
  1146. {{each dataset.relationships}} \
  1147. <li> \
  1148. ${$value.type} dataset \
  1149. <a href="#dataset/${$value.object}/view">${$value.object}</a> \
  1150. {{if $value.comment}} \
  1151. <span class="relationship_comment"> \
  1152. (${$value.comment}) \
  1153. </span> \
  1154. {{/if}} \
  1155. </li> \
  1156. {{/each}} \
  1157. </ul> \
  1158. {{if dataset.relationships.length == 0}} \
  1159. No connections to other datasets. \
  1160. {{/if}} \
  1161. </li> \
  1162. ';
  1163. CKAN.Templates.resourceForm = ' \
  1164. <form class="resource" action="" method="POST"> \
  1165. <dl> \
  1166. <dt> \
  1167. <label class="field_opt" for="Resource--url"> \
  1168. Link \
  1169. </label> \
  1170. </dt> \
  1171. <dd> \
  1172. <input id="Resource--url" name="Resource--url" type="text" value="${url}" placeholder="http://mydataset.com/file.csv" /> \
  1173. </dd> \
  1174. <dt> \
  1175. <label class="field_opt" for="Resource--type"> \
  1176. Kind \
  1177. </label> \
  1178. </dt> \
  1179. <dd> \
  1180. <select id="Resource--type" name="Resource--type"> \
  1181. <option selected="selected" value="file">File</option> \
  1182. <option value="api">API</option> \
  1183. <option value="listing">Listing</option> \
  1184. <option value="example">Example</option> \
  1185. </select> \
  1186. </dd> \
  1187. </dl> \
  1188. \
  1189. <fieldset> \
  1190. <legend> \
  1191. <h3>Optional Info</h3> \
  1192. </legend> \
  1193. <dl> \
  1194. <dt> \
  1195. <label class="field_opt" for="Resource--description"> \
  1196. Description \
  1197. </label> \
  1198. </dt> \
  1199. <dd> \
  1200. <input id="Resource--description" name="Resource--description" type="text" value="${description}" placeholder="A short description ..."/> \
  1201. </dd> \
  1202. \
  1203. \
  1204. <dt> \
  1205. <label class="field_opt" for="Resource--format"> \
  1206. Format \
  1207. </label> \
  1208. </dt> \
  1209. <dd> \
  1210. <input id="Resource--format" name="Resource--format" type="text" value="${format}" placeholder="e.g. csv, zip:csv (zipped csv), sparql"/> \
  1211. </dd> \
  1212. </fieldset> \
  1213. \
  1214. <div class="submit"> \
  1215. <input id="save" name="save" type="submit" value="Save" /> \
  1216. </div> \
  1217. </form> \
  1218. ';
  1219. CKAN.Templates.resourceCreate = ' \
  1220. <div class="resource-create"> \
  1221. <table> \
  1222. <tr class="heading"> \
  1223. <td> \
  1224. <h3>Link to data already online</h3> \
  1225. </td> \
  1226. <td><h3>or</h3></td> \
  1227. <td><h3>Upload data</h3></td> \
  1228. </tr> \
  1229. <tr> \
  1230. <td class="edit"></td> \
  1231. <td class="separator"></td> \
  1232. <td class="upload"></td> \
  1233. </tr> \
  1234. </table> \
  1235. </div> \
  1236. ';
  1237. CKAN.Templates.resourceUpload = ' \
  1238. <div class="fileupload"> \
  1239. <form action="http://test-ckan-net-storage.commondatastorage.googleapis.com/" class="resource-upload" \
  1240. enctype="multipart/form-data" \
  1241. method="POST"> \
  1242. \
  1243. <div class="fileupload-buttonbar"> \
  1244. <div class="hidden-inputs"></div> \
  1245. <label class="fileinput-button"> \
  1246. File \
  1247. </label> \
  1248. <input type="file" name="file" /> \
  1249. <span class="fileinfo"></span> \
  1250. <input type="submit" value="upload" /> \
  1251. </div> \
  1252. </form> \
  1253. <div class="messages" style="display: none;"></div> \
  1254. </div> \
  1255. ';
  1256. CKAN.Templates.resourceView = ' \
  1257. <div class="resource view" resource-id="${resource.id}"> \
  1258. <h3> \
  1259. <a href="${resource.url}" class="url">${resource.url}</a> [${resource.format}] \
  1260. </h3> \
  1261. <div class="description"> \
  1262. ${resource.description} \
  1263. </div> \
  1264. \
  1265. <div class="details subsection"> \
  1266. <h3>Additional Information</h3> \
  1267. <table> \
  1268. <thead> \
  1269. <tr> \
  1270. <th>Field</th> \
  1271. <th>Value</th> \
  1272. </tr> \
  1273. </thead> \
  1274. <tbody> \
  1275. {{each resourceDetails}} \
  1276. <tr> \
  1277. <td class="label">${$index}</td> \
  1278. <td class="value">${$value}</td> \
  1279. </tr> \
  1280. {{/each}} \
  1281. </tbody> \
  1282. </table> \
  1283. </div> \
  1284. </div> \
  1285. ';
  1286. this.CKAN || (this.CKAN = {});
  1287. this.CKAN.View || (this.CKAN.View = {});
  1288. (function (CKAN, $, _, Backbone, undefined) {
  1289. CKAN.View.DatasetListing = Backbone.View.extend({
  1290. tagName: 'ul',
  1291. constructor: function DatasetListing() {
  1292. Backbone.View.prototype.constructor.apply(this, arguments);
  1293. _.bindAll(this, 'addItem', 'removeItem');
  1294. this.el = $(this.el);
  1295. this.setCollection(this.collection);
  1296. },
  1297. setCollection: function (collection) {
  1298. if (this.collection) {
  1299. this.collection.unbind('add', this.addItem);
  1300. this.collection.unbind('remove', this.removeItem);
  1301. }
  1302. this.collection = collection;
  1303. if (collection) {
  1304. this.collection.bind('add', this.addItem);
  1305. this.collection.bind('remove', this.removeItem);
  1306. }
  1307. return this.render();
  1308. },
  1309. addItem: function (model) {
  1310. var view = new CKAN.View.DatasetListingItem({
  1311. domian: this.options.domain,
  1312. model: model
  1313. });
  1314. this.el.data(model.cid, view).append(view.render().el);
  1315. return this;
  1316. },
  1317. removeItem: function (model) {
  1318. var view = this.el.data(model.cid);
  1319. if (view) {
  1320. view.remove();
  1321. }
  1322. return this;
  1323. },
  1324. render: function () {
  1325. this.el.empty();
  1326. if (this.collection) {
  1327. this.collection.each(this.addItem);
  1328. }
  1329. return this;
  1330. },
  1331. remove: function () {
  1332. this.setCollection(null);
  1333. return Backbone.View.prototype.remove.apply(this, arguments);
  1334. }
  1335. });
  1336. CKAN.View.DatasetListingItem = Backbone.View.extend({
  1337. tagName: 'li',
  1338. className: 'dataset summary',
  1339. options: {
  1340. template: '\
  1341. <div class="header"> \
  1342. <span class="title" > \
  1343. <a href="${urls.datasetView}" ckan-attrname="title" class="editable">${displaytitle}</a> \
  1344. </span> \
  1345. <div class="search_meta"> \
  1346. {{if formats.length > 0}} \
  1347. <ul class="dataset-formats"> \
  1348. {{each formats}} \
  1349. <li>${$value}</li> \
  1350. {{/each}} \
  1351. </ul> \
  1352. {{/if}} \
  1353. </div> \
  1354. </div> \
  1355. <div class="extract"> \
  1356. {{html snippet}} \
  1357. </div> \
  1358. <div class="dataset-tags"> \
  1359. {{if tags.length}} \
  1360. <ul class="dataset-tags"> \
  1361. {{each tags}} \
  1362. <li>${$value}</li> \
  1363. {{/each}} \
  1364. </ul> \
  1365. {{/if}} \
  1366. </div> \
  1367. '
  1368. },
  1369. constructor: function DatasetListingItem() {
  1370. Backbone.View.prototype.constructor.apply(this, arguments);
  1371. this.el = $(this.el);
  1372. },
  1373. render: function () {
  1374. var dataset = this.model.toTemplateJSON();
  1375. // if 'UI' mode ...
  1376. var urls = {};
  1377. if (CKAN.UI && CKAN.UI.workspace) {
  1378. urls.datasetView = CKAN.UI.workspace.url('dataset', 'view', this.model.id);
  1379. } else {
  1380. urls.datasetView = dataset.ckan_url;
  1381. }
  1382. var data = _.extend(dataset, {
  1383. dataset: dataset,
  1384. formats: this._availableFormats(),
  1385. urls: urls
  1386. });
  1387. this.el.html($.tmpl(this.options.template, data));
  1388. return this;
  1389. },
  1390. _availableFormats: function () {
  1391. var formats = this.model.get('resources').map(function (resource) {
  1392. return resource.get('format');
  1393. });
  1394. return _.uniq(_.compact(formats));
  1395. }
  1396. });
  1397. })(CKAN, $, _, Backbone, undefined);
  1398. this.CKAN || (this.CKAN = {});
  1399. this.CKAN.View || (this.CKAN.View = {});
  1400. (function (CKAN, $, _, Backbone, undefined) {
  1401. CKAN.View.ResourceCreate = Backbone.View.extend({
  1402. initialize: function() {
  1403. this.el = $(this.el);
  1404. _.bindAll(this, 'renderMain');
  1405. this.renderMain();
  1406. this.$edit = $(this.el.find('.edit')[0]);
  1407. this.$upload = $(this.el.find('.upload')[0]);
  1408. this.editView = new CKAN.View.ResourceEditView({
  1409. model: this.model,
  1410. el: this.$edit
  1411. });
  1412. this.uploadView = new CKAN.View.ResourceUpload({
  1413. el: this.$upload,
  1414. model: this.model,
  1415. // TODO: horrible reverse depedency ...
  1416. client: CKAN.UI.workspace.client
  1417. });
  1418. },
  1419. renderMain: function () {
  1420. this.el.empty();
  1421. tmplData = {
  1422. };
  1423. var tmpl = $.tmpl(CKAN.Templates.resourceCreate, tmplData);
  1424. this.el.html(tmpl);
  1425. return this;
  1426. },
  1427. render: function () {
  1428. this.editView.render();
  1429. this.uploadView.render();
  1430. }
  1431. });
  1432. })(CKAN, $, _, Backbone, undefined);
  1433. this.CKAN || (this.CKAN = {});
  1434. this.CKAN.View || (this.CKAN.View = {});
  1435. (function (CKAN, $, _, Backbone, undefined) {
  1436. CKAN.View.ResourceUpload = Backbone.View.extend({
  1437. tagName: 'div',
  1438. // expects a client arguments in its options
  1439. initialize: function(options) {
  1440. this.el = $(this.el);
  1441. this.client = options.client;
  1442. _.bindAll(this, 'render', 'updateFormData', 'setMessage', 'uploadFile');
  1443. },
  1444. events: {
  1445. 'click input[type="submit"]': 'uploadFile'
  1446. },
  1447. render: function () {
  1448. this.el.empty();
  1449. tmplData = {
  1450. }
  1451. var tmpl = $.tmpl(CKAN.Templates.resourceUpload, tmplData);
  1452. this.el.html(tmpl);
  1453. this.$messages = this.el.find('.messages');
  1454. this.setupFileUpload();
  1455. return this;
  1456. },
  1457. setupFileUpload: function()

Large files files are truncated, but you can click here to view the full file