Advertisement
Guest User

Untitled

a guest
Nov 19th, 2017
362
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.68 KB | None | 0 0
  1.  
  2. /**
  3. * miniVU
  4. * A minimalistic HTML/JS view engine
  5. * Author: Shawn McLaughlin <shawnmcdev@gmail.com>
  6. * Website: https://github.com/shawnmcla/miniVU
  7. */
  8. class miniVU {
  9.  
  10. /**
  11. * Constructor. Initializes miniVU with the target element to append views to.
  12. * @param {string} targetElementSelector - Class or ID selector to use as the content area.
  13. * @param {string} defaultView - Default view name for the webpage, usually the "homepage". Displayed when no #/ hash is present
  14. * @param {Object} config - Used to configure miniVU. See documentation for details.
  15. */
  16. constructor(targetElementSelector, defaultView = null, config = {}) {
  17. this.VERSION = "beta 1.0";
  18. this.TITLE_PLACEHOLDER = "{{miniVU title}}";
  19. this.DEFAULT_NOT_FOUND = "<h1>404 not found</h1>";
  20. this.ERROR_MSGS = {
  21. "GENERIC_HELP": "For more help and documentation, see https://github.com/shawnmcla/miniVU",
  22. "INVALID_SELECTOR": "The targetElementSelector parameter must be a non-empty string selector. (e.g. #idSelector, .classSelector)",
  23. "ELEMENT_NOT_FOUND": "The targetElementSelector passed cannot be found in the DOM.",
  24. "VIEW_NOT_FOUND": "The associated HTML document for the view specified could not be found. Check that the file exists and is in the correct directory.",
  25. "NO_DEFAULT_VIEW": "No default view was specified. Specifying a default view is strongly recommended. To specify a default view, pass the view name as the second argument when instantiating miniVU."
  26. };
  27. this.CONFIG = {
  28. "viewsDir": "./views",
  29. "changeTitle": false,
  30. "titlePattern": "{{miniVU title}}",
  31. "customNotFound": null,
  32. "customTitles": null,
  33. };
  34.  
  35. this.ready = false;
  36. this.currentView = null;
  37. this.targetArea = null;
  38. this.defaultView = defaultView;
  39.  
  40. if (Object.keys(config).length !== 0) {
  41. this.loadConfig(config);
  42. }
  43.  
  44. let element;
  45.  
  46. if (targetElementSelector !== null && typeof (targetElementSelector == String)) {
  47. if (targetElementSelector[0] === ".") { // If selector is a class selector
  48. element = document.getElementsByClassName(targetElementSelector.substr(1))[0];
  49. } else if (targetElementSelector[0] === "#") { // If selector is an ID selector
  50. element = document.getElementById(targetElementSelector.substr(1));
  51. }
  52. else {
  53. this.errorInvalidSelector();
  54. }
  55. if (element) {
  56. this.targetArea = element;
  57. this.ready = true;
  58. }
  59. else {
  60. this.errorTargetNotFound();
  61. }
  62. } else {
  63. this.errorInvalidSelector();
  64. }
  65.  
  66. window.addEventListener('hashchange', (e) => this.handleHashChange(e, location.hash));
  67. window.addEventListener('popstate', (e) => {
  68. if (e.state && e.state.isView) {
  69. this.go(e.state.viewName);
  70. }
  71. });
  72.  
  73. if (this.ready) {
  74. this.onLoad();
  75. }
  76. }
  77.  
  78. /** Iterate through passed config keys and assigns their values to the config object if a match is found */
  79. loadConfig(config) {
  80. Object.keys(config).forEach((key) => {
  81. if (this.CONFIG.hasOwnProperty(key)) {
  82. this.CONFIG[key] = config[key];
  83. console.info(`Set config ${key} to ${config[key]}`);
  84. } else {
  85. console.error("Invalid config key: " + key + ". Ignoring.");
  86. }
  87. });
  88. }
  89.  
  90. /** Push view data to history to ensure BACK and FORWARD button functionality */
  91. pushState(viewName) {
  92. history.replaceState({ isView: true, viewName: viewName }, null, viewName === this.defaultView ? "" : "#/" + viewName);
  93. }
  94.  
  95. /** Called when a hash change is detected */
  96. handleHashChange(e, hash) {
  97. e.preventDefault();
  98. if (this.isMiniVUHash(hash)) {
  99. let viewName = hash.substr(2);
  100. if (viewName === "") {
  101. this.loadDefaultView();
  102. } else {
  103. this.go(viewName);
  104. }
  105. }
  106. }
  107.  
  108. /** Load the specified default view */
  109. loadDefaultView() {
  110. if (this.defaultView !== null) {
  111. this.go(this.defaultView, null, true);
  112. } else {
  113. console.info(ERROR_MSGS.NO_DEFAULT_VIEW);
  114. }
  115. }
  116.  
  117. /** Called once miniVU is "ready" */
  118. onLoad() {
  119. console.log("Intialized miniVU version " + this.VERSION);
  120. let hash = location.hash;
  121. if (this.isMiniVUHash(hash)) {
  122. let viewName = hash.substr(2);
  123. if (viewName !== "") {
  124. this.go(viewName);
  125. return;
  126. }
  127. }
  128. this.loadDefaultView();
  129. return;
  130. }
  131.  
  132. /** Returns true if the hash begins with "#/" */
  133. isMiniVUHash(hash) {
  134. return hash && hash.substr(0, 2) == "#/";
  135. }
  136.  
  137. /** Loads an HTML document from the filename given and returns a promise which resolves with the HTML document */
  138. loadHTMLContent(fileName) {
  139. return new Promise((resolve, reject) => {
  140. let xhr = new XMLHttpRequest();
  141.  
  142. xhr.onload = function (e) {
  143. if (xhr.readyState === 4) {
  144. if (xhr.status === 200) { // 200: HTTP OK
  145. resolve(xhr.response);
  146. } else {
  147. reject(xhr.statusText);
  148. }
  149. }
  150. };
  151.  
  152. xhr.onerror = function (e) {
  153. reject(xhr.statusText);
  154. };
  155.  
  156. xhr.open("GET", this.CONFIG.viewsDir + "/" + fileName, true);
  157. xhr.responseType = "document";
  158. xhr.send();
  159. });
  160. }
  161.  
  162. /** Clear the target area to make space for the new content */
  163. clearContent() {
  164. while (this.targetArea.firstChild) {
  165. this.targetArea.removeChild(this.targetArea.firstChild);
  166. }
  167. }
  168. /** Append the nodes of the newly loaded content to the target area */
  169. appendContent(content) {
  170. content.forEach((node) => this.targetArea.appendChild(node));
  171. }
  172. /** Append the raw HTML text to the target area */
  173. appendRaw(html) {
  174. this.targetArea.innerHTML = html;
  175. }
  176. /** Strips the content from body of the document returned */
  177. stripContent(doc) {
  178. return doc.body.childNodes;
  179. }
  180. /** Takes HTML content, makes a call to clear the target area and then a call to append the new content. */
  181. swapContent(view, content, raw = false) {
  182. this.clearContent();
  183. if (raw) {
  184. this.appendRaw(content);
  185. } else {
  186. this.appendContent(this.stripContent(content));
  187. }
  188. this.currentView = view;
  189. if (this.CONFIG.changeTitle) {
  190. let title = view;
  191. if (this.CONFIG.customTitles) {
  192. if (this.CONFIG.customTitles.hasOwnProperty(view)) {
  193. title = this.CONFIG.customTitles[view];
  194. }
  195. }
  196. document.title = this.CONFIG.titlePattern.replace(this.TITLE_PLACEHOLDER, title);
  197. }
  198. this.pushState(view);
  199. }
  200.  
  201. /** Initiate the view changing process */
  202. go(view, file) {
  203. if (view === null) {
  204. console.error("Tried to load 'null' view. Did you set the default view?");
  205. }
  206. if (view === this.customNotFound && is404) {
  207. this.swapContent("404", this.DEFAULT_NOT_FOUND, true);
  208. }
  209. if (view !== this.currentView) {
  210. this.loadHTMLContent(view + ".html")
  211. .then((doc) => this.swapContent(view, doc)
  212. .catch((err) => this.errorViewNotfound(view, err));
  213. }
  214. }
  215.  
  216. /** Error display functions. Prints a console.error message. */
  217. errorInvalidSelector(targetElementSelector) {
  218. console.error(this.ERROR_MSGS.INVALID_SELECTOR);
  219. }
  220. errorTargetNotFound(targetElementSelector) {
  221. console.error(this.ERROR_MSGS.ELEMENT_NOT_FOUND);
  222. }
  223. errorViewNotfound(view, err) {
  224. console.error(this.ERROR_MSGS.VIEW_NOT_FOUND + " View name: " + view + ", Views directory: " + this.CONFIG.viewsDir, err);
  225. /** Display default or custom "Not Found" page depending on config. */
  226. if (this.CONFIG.customNotFound && view != this.CONFIG.customNotFound) {
  227. this.go(this.CONFIG.customNotFound, null, true);
  228. } else {
  229. this.swapContent("404", this.DEFAULT_NOT_FOUND, true);
  230. }
  231. }
  232. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement