Remoto - VFS: contentSelector.js Source File
Remoto - VFS
contentSelector.js
Go to the documentation of this file.
1 
2 define( [
3  'remoto!stdlib:js/panes/pane.js',
4  'remoto!stdlib:js/paneManager/paneManager.js',
5  'remoto!stdlib:js/include/objectRegistry.js',
6  'remoto!stdlib:js/include/utils.js',
7  'include/modal/modal',
8  'remoto!stdlib:js/panes/panes/contentSelector.css',
9  ],
10  function(pane,paneManager,objectRegistry,utils,modal)
11  {
12  'use strict';
13 
35  contentSelector.prototype = new pane;
36  function contentSelector(layout)
37  {
38  pane.call(this, layout);
39 
40  this._type = "contentSelector";
41 
42  this._transitioner = null;
43  this._value = null;
44  this._video = null;
45  this._mediaTracker = null;
46  this._playing = false;
47 
48  //this._multi = false; //really only for button mode, but may be useful in the future
49 
50  //this._panes = [];
51  this._mediaItems = []; //list of paths that we care about tracking
52  }
53 
54 
64  contentSelector.prototype.createHTML = function()
65  {
66  if (this._html) return this._html;
67 
68  pane.prototype.createHTML.call(this);
69 
70  this._content.addClass("contentSelector");
71  this._content.on( "click", ".contentSelectorImage", this.imageClick.bind(this) );
72 
73  //mouse click/grab scroll
74  var THIS = this;
75  this._content.bind("mousedown", (e) => {
76 
77  //console.log(e);
78  var nn = e.target.nodeName.toLowerCase();
79  if (nn === "input")
80  return;
81 
82  var slider = this._content[0];
83  // var startX = e.pageX;
84  var startY = e.pageY;
85  // var scrollLeft = slider.scrollLeft;
86  var scrollTop = slider.scrollTop;
87  $("body").bind( "mousemove", (e) => {
88  //const x = e.pageX;
89  //slider.scrollLeft = scrollLeft + (startX-x);
90  const y = e.pageY;
91  slider.scrollTop = scrollTop + (startY-y);
92  THIS._scrolling = true;
93  return false;
94  } );
95  $("body").bind( "mouseup", (e) => {
96  $("body").unbind("mousemove");
97  $("body").unbind("mouseup");
98  setTimeout( function() {
99  THIS._scrolling = false;
100  }, 1 );
101  return false;
102  } );
103  return false;
104  });
105 
106  var events = "click touchstart";
107  this._content.on( events, ".contentSelectorMovie", this.movieClick.bind(this) );
108  this._content.on( events, ".contentSelectorMovieChapter", this.movieChapterClick.bind(this) );
109  this._content.on( events, ".contentSelectorMoviePlaystate", this.moviePlaystateClick.bind(this) );
110 
111  this._content.on( "change input", ".contentSelectorMovieVolume", this.movieVolumeChange.bind(this) );
112  this._content.on( "click", ".contentSelectorButton:not(.contentSelectorButtonInactive)",
113  this.buttonClick.bind(this) );
114  return this._html;
115  }
116 
117 
126  contentSelector.prototype.detach = function()
127  {
128  //console.log("PT detach!");
129 
130  return;
131 
132  /*
133  for (var i=0;i<this._panes.length;i++) //don't do this._panes[i].detach()... it will destroy the child panes.
134  if (this._panes[i]._object)
135  if (this._panes[i]._object.detach)
136  this._panes[i]._object.detach();
137  */
138  }
139 
140 
152  contentSelector.prototype.applySubscription = function(data,metadata)
153  {
154  //console.log("CS Apply Subscription",arguments);
155 
156  if (this._subscribed)
157  return;
158 
159  this._subscribed = true;
160 
161  //this._value = data.value;
162  this._transitioner = data.paneTransitioner;
163 
164  if (data.class)
165  this._content.addClass(data.class);
166 
167  if (this._transitioner)
168  objectRegistry.registerObject( this._transitioner, this );
169 
170  var diff = data;
171 
172  var movieCount = 0;
173 
174  this._content.empty();
175 
176  for (var i=0; i<diff.children.length; i++)
177  {
178  var block = $("<div class='contentSelectorGroup'>").appendTo(this._content);
179 
180  if (diff.children[i]["class"])
181  block.addClass(diff.children[i]["class"])
182 
183  switch(diff.children[i].type)
184  {
185  case "movies": var imgh = $("<h1>").appendTo(block).append(diff.children[i].name);
186 
187  var movies = $("<div class='contentSelectorMovies'>").appendTo(block);
188  var chapters = $("<div class='contentSelectorMovieChapters'>").appendTo(block);
189  var controls = $("<div class='contentSelectorMovieControls'>").appendTo(block);
190 
191  var _movies = diff.children[i].children;
192  for (var j=0; j<_movies.length; j++)
193  {
194  var m_id = utils.uuid();
195 
196  var md = $("<div class='contentSelectorMovie'>").appendTo(movies);
197  //var ma = $("<a href='#'>").appendTo(md);
198  md.append("<img src='"+_movies[j].thumb+"'>");
199  md.append("<br>");
200  md.append(_movies[j].text);
201  md.attr("value",_movies[j].value);
202  md.attr("m_id", m_id );
203 
204  md.attr("video",_movies[j].video || _movies[j].value);
205 
206  if (_movies[j].video) this._video = _movies[j].video; //ugly hack!
207  movieCount++; //gross
208 
209  this._mediaItems.push( _movies[j].value );
210 
211  //chapters
212  var ch = _movies[j].chapters;
213  if (ch.length > 0)
214  {
215  var mcg = $("<div class='contentSelectorMovieChaptersGroup'>").appendTo(chapters);
216  mcg.append("Chapters:<br>");
217  mcg.attr("m_id", m_id );
218  for (var k=0; k<ch.length; k++)
219  {
220  var mcgc = $("<div class='contentSelectorMovieChapter'>").appendTo(mcg);
221  mcgc.append("<img src='"+ch[k].thumb+"'>");
222  mcgc.append("<br>");
223  mcgc.append(ch[k].text);
224  mcgc.attr("value",ch[k].value);
225  }
226  }
227 
228  if (ch.length === 1)
229  { //mcg.hide();
230  mcg.remove(); //another gross hack
231  }
232 
233  if (_movies[j].html)
234  if (_movies[j].html.trim() !== '')
235  {
236  var text = $("<div class='contentSelectorMovieText'>").appendTo(block);
237  var mt = $("<div class='contentSelectorMovieTextGroup'>").appendTo(text);
238  mt.attr("m_id", m_id );
239  mt.append(_movies[j].html);
240  }
241  }
242 
243  //controls
244  //controls.append("<div class='contentSelectorMoviePlaystate'>&#9654; <b>&#8545;</b> PLAY/PAUSE</div>");
245  controls.append("<div class='contentSelectorMoviePlaystate'>&#9654;&#xFE0E; PLAY</div>");
246  controls.append("<input class='contentSelectorMovieVolume' type='range' min='0' max='1' step='.01'>");
247 
248  break;
249 
250  case "images": var imgh = $("<h1>").appendTo(block);
251  imgh.append(diff.children[i].name);
252  var imgs = diff.children[i].children;
253  for (var j=0; j<imgs.length; j++)
254  {
255  var imgd = $("<div class='contentSelectorImage'>").appendTo(block);
256  //var imga = $("<a href='#'>").appendTo(imgd);
257  $("<img src='"+imgs[j].thumb+"'>").appendTo(imgd);
258  imgd.append("<br>"+imgs[j].text);
259  imgd.attr("value",imgs[j].value);
260  }
261  block.append("<br>");
262  block.append(diff.children[i].text);
263  break;
264 
265  case "html": //console.log("HTML!");
266  //console.log(diff);
267  block.append(diff.children[i].html);
268  block.on("click", "[trigger]", this.htmlTriggerClick.bind(this));
269  break;
270 
271  case "buttons": //console.log(diff);
272  this.drawButtons(diff.children[i].children,block);
273  this._mediaItems.push( diff.children[i].value ); //add a watcher for this item
274 
275  break;
276 
277  default: block.append("Unknown contentSelector type: "+diff.children[i].type);
278  console.log(diff.children[i]);
279  break;
280  }
281  }
282 
283  if (movieCount !== 1)
284  this._video = null;
285 
286  //console.log(this._mediaItems);
287 
288  return this.applyDiff(data);
289  }
290 
291 
302  contentSelector.prototype.applyDiff = function(diff,user)
303  {
304  //console.trace("CS apply diff!");
305  //console.log(diff);
306  //console.log(arguments);
307 
308  try
309  {
310  if (diff.attributes && diff.attributes.state) //for the case where the transitioner is being changed
311  { this.value = diff.attributes.state.value;
312  //this.postUpdate( this._value, true );
313  //console.log("set value 2!");
314 
315  this.setButtonStates(diff.attributes); //a hack! kind of inefficient. should use mediaTracker!
316  }
317 
318  if (diff.value) //for the case where the group is being changed
319  { this.value = diff.value;
320  //console.log("set value 1!");
321  //console.warn( "diff.value is deprecated... write values to the transitioner instead." );
322 
323  this.setButtonStates(diff.value); //a hack! kind of inefficient. should use mediaTracker!
324  }
325  }
326  catch(e)
327  {
328  console.error(e);
329  }
330  }
331 
332 
342  contentSelector.prototype.applySettings = function(settings)
343  {
344  //console.log("CS apply settings");
345  //console.trace(arguments);
346  }
347 
348 
357  contentSelector.prototype.destroy = function()
358  {
359  if (this._transitioner)
360  objectRegistry.unregisterObject( this._transitioner, this );
361 
362  this.value = null; //will also unregister the current media object
363  this._mediaItems = [];
364 
365  pane.prototype.destroy.call(this);
366  }
367 
368 
379  contentSelector.prototype.htmlTriggerClick = function(evt)
380  {
381  var t = $(evt.currentTarget);
382  var r = JSON.parse( t.attr("trigger") );
383 
384  for(var tr in r)
385  this._html.trigger(tr,r[tr]);
386 
387  return false;
388  }
389 
390 
401  contentSelector.prototype.movieClick = function(evt)
402  {
403  var mov = $(evt.currentTarget);
404  // var value = mov.attr("video") || mov.attr("value");
405  var value = mov.attr("value");
406  this.value = value;
407 
408  this._video = mov.attr("video");
409 
410  // this.play = false;
411 
412  var u = { attributes: { state: { value: value } } };
413 
414  // objectRegistry.applyDiff( this._transitioner, u, null, this );
415  this.postUpdate( value );
416 
417  return false;
418  }
419 
420 
431  contentSelector.prototype.movieChapterClick = function(evt)
432  {
433  var chapter = $(evt.currentTarget);
434  var chapterValue = chapter.attr("value");
435 
436  var u = { attributes: {} };
437  u.attributes.play = { value: true };
438  u.attributes.chapter = { value: chapterValue };
439 
440  // objectRegistry.applyDiff( this._transitioner, u, null, this );
441  this.postMovieUpdate( this._video || this._value, u );
442 
443  return false;
444  }
445 
456  contentSelector.prototype.movieVolumeChange = function(evt)
457  {
458  //console.log("movieVolumeChange",evt);
459 
460  var v = $(evt.currentTarget).val();
461  var u = { attributes: { volume: { value: v } } };
462 
463  // objectRegistry.applyDiff( this._path, u, null, this );
464  this.postMovieUpdate( this._video || this._value, u );
465 
466  return false;
467  }
468 
479  contentSelector.prototype.moviePlaystateClick = function(evt)
480  {
481  var u = { attributes: {} };
482  u.attributes.play = { value: !this._playing };
483 
484  this.play = !this._playing;
485  //console.log(this._playing);
486  //console.log(this._video);
487 
488  this.postMovieUpdate( this._video || this._value, u );
489 
490  return false;
491  }
492 
503  contentSelector.prototype.imageClick = function(evt)
504  {
505  var img = $(evt.currentTarget);
506  var value = img.attr("value");
507  this.value = value;
508 
509  var u = { attributes: { state: { value: value } } };
510 
511  // objectRegistry.applyDiff( this._transitioner, u, null, this );
512  this.postUpdate( value );
513 
514  return false;
515  }
516 
528  contentSelector.prototype.__defineSetter__("value", function(v)
529  {
530  //console.trace(v);
531 
532  if (v !== this._value)
533  {
534  if (this._mediaTracker)
535  { this._mediaTracker.destroy();
536  this._mediaTracker = null;
537  }
538 
539  this._value = objectRegistry.extendDefinition(v,this._value);
540  //this._value = v;
541  //console.log("value: "+this._value);
542 
543  this._content.find(".contentSelectorImageSelected").removeClass("contentSelectorImageSelected");
544  this._content.find(".contentSelectorImage[value='"+v+"']").addClass("contentSelectorImageSelected");
545 
546  this._content.find(".contentSelectorMovieSelected").removeClass("contentSelectorMovieSelected");
547  this._content.find(".contentSelectorMovieControls").hide();
548  this._content.find(".contentSelectorMovieTextGroup").hide();
549  this._content.find(".contentSelectorMovieChaptersGroup").hide();
550  var m = this._content.find(".contentSelectorMovie[value='"+v+"']").addClass("contentSelectorMovieSelected");
551  if (m.length)
552  {
553  var m_id = m.attr("m_id");
554  this._content.find("[m_id='"+m_id+"']").show();
555  this._content.find(".contentSelectorMovieControls").show();
556  }
557 
558  if (this._value && this._mediaItems.indexOf(this._value) > -1)
559  { this._mediaTracker = new mediaTracker(this._value,this);
560  }
561  }
562  });
563 
570  contentSelector.prototype.__defineSetter__("chapter", function(chapter)
571  {
572  var c = (chapter.value) ? chapter.value : chapter;
573 
574  //console.log("chapter: "+c);
575  //console.log(chapter);
576 
577  this._content.find(".contentSelectorMovieChapterSelected").removeClass("contentSelectorMovieChapterSelected");
578  //this._content.find(".contentSelectorMovieChaptersGroup:visible").find(".contentSelectorMovieChapter[value='"+c+"']").addClass("contentSelectorMovieChapterSelected");
579  this._content.find(".contentSelectorMovieChaptersGroup").find(".contentSelectorMovieChapter[value='"+c+"']").addClass("contentSelectorMovieChapterSelected");
580  });
581 
588  contentSelector.prototype.__defineSetter__("play", function(play)
589  {
590  var p = play.value;
591 
592  if (play.value === undefined) {
593  p = play;
594  }
595 
596  //console.log("play: "+p);
597 
598  this._playing = p;
599 
600  if (p)
601  {
602  this._content.find( ".contentSelectorMoviePlaystate" ).html("<span class='video-pause'>PAUSE</span>");
603  }
604  else
605  {
606  this._content.find( ".contentSelectorMoviePlaystate" ).html("<span class='video-play'>PLAY</span>");
607  }
608  });
609 
616  contentSelector.prototype.__defineSetter__("volume", function(v)
617  {
618  var vol = Math.min( Math.max( v.value, 0 ), 1 );
619 
620  //console.log("volume: "+vol);
621 
622  this._content.find( ".contentSelectorMovieVolume" ).val(vol);
623  });
624 
627  /*
628  contentSelector.prototype.postUpdate = function(u)
629  {
630  var update = {};
631 
632  // update[this._path] = u;
633  update[this._transitioner] = u;
634 
635  return this.postUpdateCallback.call(this,update);
636  }
637  */
638 
650  contentSelector.prototype.postUpdate = function(v,target)//,selfOnly)
651  {
652  target = target || this._transitioner;
653  target = utils.cleanPath( target );
654 
655  var u1 = { attributes: { state: { value: v } } };
656  var u2 = { value: v };
657 
658  var update = {};
659  //if (!selfOnly)
660  update[target] = u1;
661  update[this._path] = u2; //we don't need this... just sending to the pane transitioner is enough
662 
663  return this.postUpdateCallback.call(this,update);
664  }
665 
677  contentSelector.prototype.postMovieUpdate = function(m,u)
678  {
679  m = utils.cleanPath( m );
680 
681  var update = {};
682  update[m] = u;
683 
684  return this.postUpdateCallback.call(this,update);
685  }
686 
687  /*
688  contentSelector.prototype.postButtonUpdate = function(m,u)
689  {
690  var update = {};
691  update[m] = u;
692 
693  return this.postUpdateCallback.call(this,update);
694  }
695  */
696 
707  contentSelector.prototype.drawButtons = function(buttons,block)
708  {
709  var max = 0;
710 
711  for (var b in buttons.buttons)
712  {
713  var button = buttons.buttons[b];
714 
715  var t = $("<div class='contentSelectorButton'>");
716  t.text( button.name );
717 
718  var c = ["height","width","top","bottom","left","right"];
719  for (var i=0;i<c.length;i++)
720  if (button.hasOwnProperty(c[i]))
721  t.css( c[i], button[c[i]] );
722 
723  t.addClass( button["class"] );
724  t.attr( "attribute", button.attribute );
725  t.attr( "value", button.value );
726  if(button.data)
727  t.attr("data-light",button.data);
728 
729 
730  if (button.target)
731  t.attr("target",utils.cleanPath(button.target) );
732 
733  if (button.modal)
734  {
735  var m_id = "modal_"+utils.uuid();
736 
737  var m = $("<div class='contentSelectorModalContent'>");
738  m.attr("id", m_id);
739  m.append( button.modalHTML );
740  m.appendTo( block );
741 
742  t.attr("modal",m_id);
743  }
744 
745  if (button.toggle)
746  t.attr("toggle",button.toggle);
747 
748  if (button.trigger)
749  t.attr("trigger", JSON.stringify(button.trigger) );
750 
751  if (!button.enabled) t.addClass( "contentSelectorButtonInactive" );
752  t.appendTo( block );
753 
754  if (button.top && button.top + button.height > max)
755  max = button.top + button.height;
756  if (button.bottom && button.bottom + button.height > max)
757  max = button.bottom + button.height;
758  }
759 
760  block.css( { "height": max+"px" } );
761 
762  for (var g in buttons.groups)
763  {
764  //console.log(g);
765  }
766  }
767 
768  /*
769  contentSelector.prototype.applyFilter = function(evt)
770  {
771 
772  var checkDatas = $('div.contentSelectorButton.building_unit').data();
773  var priceRange = $('input.price').value();
774  var bedroomsRange = $('input.bedrooms').value();
775  var bathroomsRange = $('input.bathrooms').value();
776  var tarrace = $('input.tarrace').value();
777  for(var data in checkDatas)
778  {
779 
780  if(data.bedrooms > bedroomsRange[0] && data.bedrooms < bedroomsRange[1]
781  && data.price > priceRange[0] && data.price < priceRange[1]
782  && data.bathrooms > bathroomsRange[0] && data.bathrooms < bathroomsRange[1]
783  && data.tarrace == tarrace)
784  {
785 
786  }
787  }
788  }
789  */
790 
799  //set the current state of any buttons
800  contentSelector.prototype.setButtonStates = function(state)
801  {
802  //console.log(state);
803 
804  if (typeof(state)=="object")
805  for (var attr in state)
806  {
807  //if(attr != "all")
808  //{
809  var v = state[attr].value;
810  //var v = state[attr];
811  this._content.find(".contentSelectorButton[attribute='"+attr+"']").removeClass("contentSelectorButtonSelected");
812  this._content.find(".contentSelectorButton[attribute='"+attr+"'][value='"+v+"']").addClass("contentSelectorButtonSelected");
813  //}
814  }
815  }
816 
826  contentSelector.prototype.buttonClick = function(evt)
827  {
828  //console.log("button click!");
829  //console.log(evt);
830  //console.log( this._value );
831 
832  var t = $(evt.currentTarget);
833  var attrs = t.attr("attribute");
834  var target = t.attr("target");
835  var modalID = t.attr("modal");
836  var toggle = t.attr("toggle");
837  var val = t.attr("value");
838  var attr;
839 
840  attrs = attrs ? attrs.split(",") : [];
841  target = utils.cleanPath( target );
842 
843  var u = { attributes:{} };
844  var u2 = { value: {} };
845 
846  for (var i=0; i<attrs.length; i++)
847  {
848  attr = attrs[i];
849 
850  if (attr === '')
851  continue;
852 
853  if (toggle)
854  {
855  if (this._value && this._value[attr])
856  { val = this._value[attr].value == "true" || this._value[attr].value === true ? false : true;
857  this._value[attr].value = val;
858  }
859  }
860 
861  //console.log("click: "+attr+" "+val+" :: "+this._value);
862 
863  u.attributes[attr] = { value: val };
864  u2.value[attr] = { value:val };
865  }
866 
867  var update = {};
868  update[ target ] = u;
869  update[ this._path ] = u2;
870 
871  this.postUpdateCallback.call(this,update);
872  this.setButtonStates( u2.value );
873 
874 
875  if (modalID)
876  {
877  //console.log("would open modal "+modalID+" here!");
878  var self = this;
879  var bModal = {
880  exec: function(_modal)
881  {
882  var mount = _modal._center;
883  //var mount = _modal._mount;
884 
885  var m = $("<div class='contentSelectorModal'/>").appendTo(mount);
886 
887  m.append( $("#"+modalID).clone().show() );
888 
889  var bs = $("<div class='contentSelectorModalButtons'>").appendTo(m);
890  var b = $("<button>").text("Close");
891  b.bind("click", function() { _modal.execEvent("close"); return false; });
892  b.appendTo(bs);
893 
894  setTimeout( function() {
895  mount.bind("click", function() { _modal.execEvent("close"); return false; });
896  }, 100 );
897 
898  m.bind("click", false);
899 
900  m.on("click","div.contentSelectorImage",function(e) {
901 
902  console.log("allowing event to bubble!");
903 
904  $(e.target).parent().siblings().children().removeClass("active");
905  $(e.target).addClass("active");
906  self.imageClick(e);
907 
908 
909  } );
910 
911  $(window).trigger("modalOpen");
912 
913 
914 
915  },
916  };
917 
918  modal.exec( bModal, { "close":false } );
919  }
920  }
921 
940  //a holding object to track media activity
941  function mediaTracker(path,selector)
942  {
943  //console.log("new media tracker: "+path);
944 
945  this._path = utils.cleanPath(path);
946  this._subscribed = false;
947  this._type = null;
948  this._selector = selector;
949 
950  objectRegistry.registerObject( this._path, this );
951  }
952 
961  mediaTracker.prototype.destroy = function()
962  {
963  //console.log("destroy media tracker");
964 
965  objectRegistry.unregisterObject( this._path, this );
966 
967  this._selector = null;
968  }
969 
980  mediaTracker.prototype.applySubscription = function(data,metadata)
981  {
982  //console.log("Media Tracker apply subscription");
983  //console.log(arguments);
984 
985  this._type = metadata.type;
986 
987  this.applyDiff(data);
988  }
989 
1000  mediaTracker.prototype.applyDiff = function(diff,user)
1001  {
1002  //console.log("Media Tracker apply diff",this,arguments);
1003 
1004  switch( this._type )
1005  {
1006  case "video":
1007  case "videoPlayer": //console.log(diff);
1008  if (diff.attributes)
1009  for (var a in diff.attributes)
1010  if (this._selector.__proto__.hasOwnProperty(a))
1011  this._selector[a] = diff.attributes[a];
1012  //else
1013  // console.log("selector has no attribute: "+a);
1014  break;
1015 
1016  case "requireModule": //console.log("require module tracker!");
1017  //console.log(diff);
1018  if (diff.attributes)
1019  this._selector.setButtonStates(diff.attributes);
1020  break;
1021 
1022  default: //console.log("Type: "+this._type);
1023  break;
1024  }
1025  }
1026 
1027  return contentSelector;
1028  }
1029 );
getter html
Get the html color representation of this object.
setButtonStates(state)
applySettings(settings)
postUpdate(v, target)
contentSelector(layout)
drawButtons(buttons, block)
setter play
a setter DOCME
applyDiff(diff, user)
setter chapter
a setter DOCME
setter value
a setter DOCME
applySubscription(data, metadata)
moviePlaystateClick(evt)
setter volume
a setter DOCME
setter user
a setter DOCME
applyDiff(diff, user)
applySubscription(data, metadata)
mediaTracker(path, selector)
getter y
a getter DOCME
find(id, type)
Find a registered object, optionally by type.
registerObject(id, o, nosubscribe)
Register an object in the registry, and call the pathAddedCallback.
unregisterObject(id, o, silent, nounsubscribe, now)
Unregister an object from the registry.
setter postUpdate
Assign a callback to the paneFactory class for VFS_node::submit() commands.
postUpdateCallback()
Create a pane which will be mounted into a paneManager layout.
destroy()
DOCME.
createHTML()
pane(layout, object)
The paneManager manages panes in a user layout.
getter path
a getter DOCME
getter state
a getter DOCME
applyDiff(diff, user)
update(dirty)
Process the _queue and apply the settings.
Utility functions for javascript clients.
uuid()
Generate a universally unique identifier.
cleanPath(path)
Clean and normalize a resource path.
text(value, options)
metadata(paths)