| @ -0,0 +1,88 @@ | |||
| {% extends 'base.html' %} | |||
| {% block content %} | |||
| <div class="tabs is-toggle is-fullwidth" id="tabs"> | |||
| <!-- | |||
| <button class="tablinks" onclick="openTab(event, 'Songs')">Songs</button> | |||
| <button class="tablinks" onclick="openTab(event, 'Albums')">Albums</button> | |||
| --> | |||
| <ul> | |||
| <li class="is-active" id="tablinks"> | |||
| <a onclick="openTab(event, 'Songs')"> | |||
| <span class="icon is-small"><i class="fa fa-image"></i></span> | |||
| <span>Canciones</span> | |||
| </a> | |||
| </li> | |||
| <li id="tablinks"> | |||
| <a onclick="openTab(event, 'Albums')"> | |||
| <span class="icon is-small"><i class="fa fa-music"></i></span> | |||
| <span>Álbumes</span> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| <div id="Songs" class="tabcontent is-active"> | |||
| <h2>Song List</h2> | |||
| {% if songs %} | |||
| <table id="songTable" class="display"> | |||
| <thead> | |||
| <tr> | |||
| <th>Pista</th> | |||
| <th>Título</th> | |||
| <th>Autor</th> | |||
| <th>Álbum</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for song in songs %} | |||
| <tr> | |||
| <td>{{ song.pista }}</td> | |||
| <td><a href="{{ url_for('paginas.song', song_id=song.id) }}">{{ song.title }}</a></td> | |||
| <td>{{ song.author }}</td> | |||
| <td><a href="{{ url_for('paginas.album', album_id=song.album.id) }}">{{ song.album.name }}</a></td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No songs found.</p> | |||
| {% endif %} | |||
| </div> | |||
| <div id="Albums" class="tabcontent"> | |||
| <h2>Album List</h2> | |||
| {% if albums %} | |||
| <table id="albumTable" class="display"> | |||
| <thead> | |||
| <tr> | |||
| <th>Cover</th> | |||
| <th>Nombre</th> | |||
| <th>Artista</th> | |||
| <th>Año</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for album in albums %} | |||
| <tr> | |||
| <td> | |||
| {% if album.cover_image %} | |||
| <img src="{{ url_for('paginas.uploaded_file', filename=album.cover_image) }}" alt="{{ album.name }}" style="width:50px;height:50px;"> | |||
| {% else %} | |||
| Sin imágen | |||
| {% endif %} | |||
| </td> | |||
| <td><a href="{{ url_for('paginas.album', album_id=album.id) }}">{{ album.name }}</a></td> | |||
| <td>{{ album.artist }}</td> | |||
| <td>{{ album.year }}</td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No albums found.</p> | |||
| {% endif %} | |||
| <hr> | |||
| <a href="{{ url_for('paginas.add_album') }}" class="button">Añadir nuevo álbum</a> | |||
| </div> | |||
| {% endblock %} | |||
| @ -0,0 +1,4 @@ | |||
| { | |||
| "presets": ["env"], | |||
| "plugins": ["transform-object-rest-spread"] | |||
| } | |||
| @ -0,0 +1,4 @@ | |||
| dist/ | |||
| docs/ | |||
| scripts/ | |||
| webpack.config.js | |||
| @ -0,0 +1,35 @@ | |||
| { | |||
| "env": { | |||
| "browser": true, | |||
| "es6": true | |||
| }, | |||
| "extends": "eslint:recommended", | |||
| "parserOptions": { | |||
| "sourceType": "module", | |||
| "ecmaFeatures": { | |||
| "experimentalObjectRestSpread": true | |||
| } | |||
| }, | |||
| "rules": { | |||
| "indent": [ | |||
| "error", | |||
| 4 | |||
| ], | |||
| "linebreak-style": [ | |||
| "error", | |||
| "unix" | |||
| ], | |||
| "quotes": [ | |||
| "error", | |||
| "single" | |||
| ], | |||
| "semi": [ | |||
| "error", | |||
| "always" | |||
| ], | |||
| "no-unused-vars": [ | |||
| "warn", | |||
| "all" | |||
| ] | |||
| } | |||
| } | |||
| @ -0,0 +1,12 @@ | |||
| # These are supported funding model platforms | |||
| github: VizuaaLOG | |||
| patreon: vizuaalog | |||
| open_collective: # Replace with a single Open Collective username | |||
| ko_fi: # Replace with a single Ko-fi username | |||
| tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | |||
| community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | |||
| liberapay: # Replace with a single Liberapay username | |||
| issuehunt: # Replace with a single IssueHunt username | |||
| otechie: # Replace with a single Otechie username | |||
| custom: ['https://www.paypal.me/tomerbe'] | |||
| @ -0,0 +1,29 @@ | |||
| --- | |||
| name: "\U0001F41EBug report" | |||
| about: Create a bug report to help us improve | |||
| title: '' | |||
| labels: bug, is-unconfirmed | |||
| assignees: VizuaaLOG | |||
| --- | |||
| **Versions** | |||
| BulmaJS: | |||
| Browser: | |||
| Operating system: | |||
| **Describe the bug** | |||
| A clear and concise description of what the bug is. | |||
| **To Reproduce** | |||
| Steps to reproduce the behavior: | |||
| 1. Go to '...' | |||
| 2. Click on '....' | |||
| 3. Scroll down to '....' | |||
| 4. See error | |||
| **Expected behavior** | |||
| A clear and concise description of what you expected to happen. | |||
| **Screenshots** | |||
| If applicable, add screenshots to help explain your problem. | |||
| @ -0,0 +1,20 @@ | |||
| --- | |||
| name: "✨Feature request" | |||
| about: Suggest an idea for this project | |||
| title: '' | |||
| labels: is-feature | |||
| assignees: VizuaaLOG | |||
| --- | |||
| **Is your feature request related to a problem? Please describe.** | |||
| A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] | |||
| **Describe the solution you'd like** | |||
| A clear and concise description of what you want to happen. | |||
| **Describe alternatives you've considered** | |||
| A clear and concise description of any alternative solutions or features you've considered. | |||
| **Additional context** | |||
| Add any other context or screenshots about the feature request here. | |||
| @ -0,0 +1,29 @@ | |||
| --- | |||
| name: Bug report | |||
| about: Create a report to help us improve | |||
| title: | |||
| labels: | |||
| assignees: | |||
| --- | |||
| **Versions** | |||
| BulmaJS: | |||
| Browser: | |||
| Operating system: | |||
| **Describe the bug** | |||
| A clear and concise description of what the bug is. | |||
| **To Reproduce** | |||
| Steps to reproduce the behavior: | |||
| 1. Go to '...' | |||
| 2. Click on '....' | |||
| 3. Scroll down to '....' | |||
| 4. See error | |||
| **Expected behavior** | |||
| A clear and concise description of what you expected to happen. | |||
| **Screenshots** | |||
| If applicable, add screenshots to help explain your problem. | |||
| @ -0,0 +1,20 @@ | |||
| --- | |||
| name: Feature request | |||
| about: Suggest an idea for this project | |||
| title: | |||
| labels: | |||
| assignees: | |||
| --- | |||
| **Is your feature request related to a problem? Please describe.** | |||
| A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] | |||
| **Describe the solution you'd like** | |||
| A clear and concise description of what you want to happen. | |||
| **Describe alternatives you've considered** | |||
| A clear and concise description of any alternative solutions or features you've considered. | |||
| **Additional context** | |||
| Add any other context or screenshots about the feature request here. | |||
| @ -0,0 +1,5 @@ | |||
| node_modules | |||
| mix-manifest.json | |||
| .DS_Store | |||
| index.html | |||
| .idea | |||
| @ -0,0 +1,4 @@ | |||
| eslint: | |||
| enabled: true | |||
| config_file: .eslintrc.json | |||
| ignore_file: .eslintignore | |||
| @ -0,0 +1,11 @@ | |||
| language: node_js | |||
| node_js: | |||
| - "8.14" | |||
| install: | |||
| - npm install | |||
| script: | |||
| - npm run lint | |||
| - npm run dev | |||
| - npm run test | |||
| @ -0,0 +1,46 @@ | |||
| # Contributor Covenant Code of Conduct | |||
| ## Our Pledge | |||
| In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. | |||
| ## Our Standards | |||
| Examples of behavior that contributes to creating a positive environment include: | |||
| * Using welcoming and inclusive language | |||
| * Being respectful of differing viewpoints and experiences | |||
| * Gracefully accepting constructive criticism | |||
| * Focusing on what is best for the community | |||
| * Showing empathy towards other community members | |||
| Examples of unacceptable behavior by participants include: | |||
| * The use of sexualized language or imagery and unwelcome sexual attention or advances | |||
| * Trolling, insulting/derogatory comments, and personal or political attacks | |||
| * Public or private harassment | |||
| * Publishing others' private information, such as a physical or electronic address, without explicit permission | |||
| * Other conduct which could reasonably be considered inappropriate in a professional setting | |||
| ## Our Responsibilities | |||
| Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. | |||
| Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. | |||
| ## Scope | |||
| This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. | |||
| ## Enforcement | |||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tom@tomerbe.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. | |||
| Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. | |||
| ## Attribution | |||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] | |||
| [homepage]: http://contributor-covenant.org | |||
| [version]: http://contributor-covenant.org/version/1/4/ | |||
| @ -0,0 +1,3 @@ | |||
| If you would like to contribute a plugin, fix a bug or fix some horrible typo in the docs please submit a PR if you can, alternativly create an issue! Please note, while the code base doesn't follow any strict style, please try to ensure your PRs follow the general convention you see (there may be times the convention isn't followed, if you see this submit an issue/PR and slap me on the wrist :)). Any PR/issue at this time will not be rejected but may have some alternation to the code. | |||
| If you are submitting a plugin, please also submit the relevant documentation. | |||
| @ -0,0 +1,21 @@ | |||
| MIT License | |||
| Copyright (c) 2017 Thomas Erbe | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| of this software and associated documentation files (the "Software"), to deal | |||
| in the Software without restriction, including without limitation the rights | |||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| copies of the Software, and to permit persons to whom the Software is | |||
| furnished to do so, subject to the following conditions: | |||
| The above copyright notice and this permission notice shall be included in all | |||
| copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| SOFTWARE. | |||
| @ -0,0 +1,27 @@ | |||
| **IMPORTANT: Please do not create a Pull Request without creating an issue first.** | |||
| *Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request.* | |||
| Please provide enough information so that others can review your pull request: | |||
| <!-- You can skip this if you're fixing a typo or adding an app to the Showcase. --> | |||
| Explain the **details** for making this change. What existing problem does the pull request solve? | |||
| <!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. --> | |||
| **Test plan (required)** | |||
| Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. | |||
| <!-- Make sure tests pass on both Travis and Circle CI. --> | |||
| **Code formatting** | |||
| Please ensure that running `npm run lint` passes. Please correct any issues. | |||
| **Closing issues** | |||
| Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such). | |||
| **Please delete all comments here before submitting your PR, ensuring you have followed the guidelines.** | |||
| @ -0,0 +1,23 @@ | |||
| BulmaJS | |||
| ======== | |||
|  | |||
|  | |||
| [](https://travis-ci.org/VizuaaLOG/BulmaJS) | |||
| [](https://houndci.com) | |||
| [](https://github.com/VizuaaLOG/BulmaJS/blob/master/LICENSE) | |||
| This is an unofficial JavaScript extension for the [Bulma CSS framework](http://bulma.io). | |||
| **BulmaJS is still in development, you can keep track of current, pending, completed and planned tasks on the official [Trello board](https://trello.com/b/XS93oQNi/bulmajs).** | |||
| **tl;dr** | |||
| GIMME THE DOCS! [Well here you go](https://bulmajs.tomerbe.co.uk/). | |||
| Bulma is an excellent CSS framework and the perfect alternative to Bootstrap. One of the great things about Bulma is that it's **JUST** CSS; there is no JavaScript included. This makes it *super* lightweight. Some components *do* require JavaScript however, and that is where BulmaJS comes in! | |||
| BulmaJS is written in ES6 and provides a plugin based system for adding interaction to the CSS components. BulmaJS is an ongoing project. As new components are added to Bulma, if they require JavaScript then they will also be added to BulmaJS. We are not limited to the core components though. If you have a plugin you would like incorporated, please submit a PR. If you have written a CSS extension to Bulma and would like some JS written, please submit an issue and we may write the JS functionality for you. | |||
| # Contributing | |||
| If you would like to contribute a plugin, fix a bug or fix some horrible typo in the docs, then please submit a PR if you can! Alternatively, create an issue. Please note, while the code base doesn't follow any strict style, try to ensure your PRs follow the general convention that you see. There may be times the convention isn't followed, so if you see this submit an issue/PR and slap me on the wrist :). Any PR/issue at this time will not be rejected but may have some alteration to the code. | |||
| If you are submitting a plugin, please also submit the relevant documentation. | |||
| @ -0,0 +1,102 @@ | |||
| # 0.12.2 | |||
| + **Bug** [131](https://github.com/VizuaaLOG/BulmaJS/issues/131) Navbar will now detect if window is defined before trying to attach sticky events | |||
| + **Bug** [138](https://github.com/VizuaaLOG/BulmaJS/issues/138) Do not assume the navbar-burger element always exists | |||
| # 0.12.1 | |||
| + **Bug** [120](https://github.com/VizuaaLOG/BulmaJS/issues/120) Panel tabs will now properly take into account `is-active`. On `init` this will become the starting tab | |||
| # 0.12.0 | |||
| + **Feature** [107](https://github.com/VizuaaLOG/BulmaJS/issues/107) Add support for the navbar dropdown | |||
| + **Feature** [94](https://github.com/VizuaaLOG/BulmaJS/issues/94) Add a new global `onLoaded` callback that can be defined within the `window.bulmaOptions` object. This is called once the library has been loaded and the `DOMContentLoaded` event has been fired. | |||
| + **Feature** [102](https://github.com/VizuaaLOG/BulmaJS/issues/102) Add support for panel tabs. | |||
| + **Tweak** [95](https://github.com/VizuaaLOG/BulmaJS/issues/95) The `parseDocument` method will now check the given context against the internal plugins | |||
| + **Refactor** Add a `Bulma.getGlobalConfig(key: string, default: mixed)` method to the core. This allows easy access to properties within the `window.bulmaOptions` object and accepts a default as it's second parameter, this is `null` by default. | |||
| # 0.11.0 | |||
| + **Feature** Add a `getFilename` method to the file plugin. | |||
| + **Feature** Add a `setActive` method to the tabs plugin. This allows for programatically changing the active tab. | |||
| + **Feature** Add the ability to enable / disable a navbar's hide on scroll behavior at any time with `enableHideOnScroll` and `disableHideOnScroll` | |||
| + **Feature** Add the ability to enable / disable a navbar's sticky behaviour at any time with `enableSticky` and `disableSticky`. | |||
| + **Feature** Allow a plugin instance to be grabbed from an existing element. For example `Bulma('.navbar').data('navbar')` will return the navbar plugin instance associated with the element. | |||
| + **Feature** [#71](https://github.com/VizuaaLOG/BulmaJS/issues/71) The modal plugin can have it's HTML content loaded via an AJAX request using the new `bodyUrl` option. | |||
| + **Bug** [#71](https://github.com/VizuaaLOG/BulmaJS/issues/71) Fixed a typo within the Alert plugin that checks if the alert should be destroyed | |||
| + **Refactor** Adjust how options are internally handled, renaming variables to `config` and implementing a `ConfigBag` helper class. | |||
| + **Refactor** Remove the create method in favour of using the plugin's constructor method. | |||
| + **Refactor** Rewrite the API so that you can select an element `Bulma(selector)` and then call the relevant plugin. The core will then automatically pss the selector element as the root, or create an empty div as the root if nothing is provided. | |||
| # 0.10.4 | |||
| + **Bug** [#100](https://github.com/VizuaaLOG/BulmaJS/issues/100) Fix the `npm run production` script failing | |||
| # 0.10.3 | |||
| + **Bug** Change the default hide offset value to null rather than 0 [@luksurious](https://github.com/luksurious) [Pull Request](https://github.com/VizuaaLOG/BulmaJS/pull/91). | |||
| # 0.10.2 | |||
| + **Bug** [#90](https://github.com/VizuaaLOG/BulmaJS/issues/90) Hiding navbar on scrolls creates empty space, not reappearing as expected [@luksurious](https://github.com/luksurious) [Pull Request](https://github.com/VizuaaLOG/BulmaJS/pull/91). | |||
| # 0.10.1 | |||
| + **Bug** `Backported from master` [#88](https://github.com/VizuaaLOG/BulmaJS/issues/88) Ensure the keyup event handler is removed then a Modal is destroyed. Thanks to [@luksurious](https://github.com/luksurious) [Pull Request](https://github.com/VizuaaLOG/BulmaJS/pull/89). | |||
| # 0.10.0 | |||
| + **Feature** Brand new documentation! To improve the ease of updating the docs the documentation has been ported to a new static site build, [Jigsaw](https://jigsaw.tighten.co/). As part of this it's also had a freshen up, there's still some things that needs tweaking but we're mostly there. The docs have also been moved out of the main repository, and are now in a [BulmaJS-Docs](https://github.com/VizuaaLOG/BulmaJS-Docs) repository. You can access the new docs by visiting [bulmajs.tomerbe.co.uk](https://bulmajs.tomerbe.co.uk). If you would still like to use the pre 0.10 docs you can do so by visiting [https://vizuaalog.github.io/BulmaJS/](https://vizuaalog.github.io/BulmaJS/); | |||
| + **Feature** [#70](https://github.com/VizuaaLOG/BulmaJS/issues/70) The automatic document parsing can be disabled using the `bulmaOptions` object. | |||
| + **Tweak** [#70](https://github.com/VizuaaLOG/BulmaJS/issues/66) The `traverseDOM` method now accepts an optional parameter specifying an HTMLElement to use as the scope when parsing the document. | |||
| + **Tweak** The `destroyOnConfirm` and `destroyOnCancel` options on the alert plugin have been moved to the buttons object and renamed to `destroy`. | |||
| + **Tweak** The `closeOnConfirm` and `closeOnCancel` options on the alert plugin have been moved to the buttons object and renamed to `close`. | |||
| + **Tweak** The `onConfirm` property on the alert object has been moved to the `confirm` object and renamed to `onClick`. | |||
| + **Tweak** The `onCancel` property on the alert object has been moved to the `cancel` object and renamed to `onClick`. | |||
| # 0.9.1 | |||
| + **Feature** [#66](https://github.com/VizuaaLOG/BulmaJS/issues/66) The alert plugin now has `closeOnCancel`, `closeOnConfirm`, `destroyOnCancel` and `destroyOnConfirm` options. Thanks to [@alexcanana](https://github.com/alexcanana). | |||
| + **Bug** [#69](https://github.com/VizuaaLOG/BulmaJS/issues/69) The modal plugin now correctly handles the `is-clipped` class. Thanks to [@alexcanana](https://github.com/alexcanana). | |||
| # 0.9.0 | |||
| + **Feature** [#64](https://github.com/VizuaaLOG/BulmaJS/issues/64) The alert plugin now has a `showHeader` option. This defaults to `true`, setting it to `false` will hide the alert's header. | |||
| + **Feature** [#64](https://github.com/VizuaaLOG/BulmaJS/issues/64) The alert plugin now has `destroyOnConfirm` and `destroyOnCancel` options. These both default to `true` setting them to `false` will prevent the alert from destroying itself when the relevant button is clicked. | |||
| + **Feature** [#64](https://github.com/VizuaaLOG/BulmaJS/issues/64) The alert plugin now has additional options for buttons. Supplying an object will allow you to also provide additional classes for each button. More details in the alert documentation. | |||
| + **Tweak** [#65](https://github.com/VizuaaLOG/BulmaJS/issues/65) The alert callbacks now include an `event` argument. Thanks to [@alexcanana](https://github.com/alexcanana). | |||
| + **Bug** Fixed a bug that caused `.modal` and `modal` classes to be applied to a modal | |||
| # 0.8.1 | |||
| + **Bug** [63](https://github.com/VizuaaLOG/BulmaJS/issues/63) Use a helper `each` method instead of `forEach`. This is to add support for IE11. | |||
| # 0.8.0 | |||
| + **Feature** [#47](https://github.com/VizuaaLOG/BulmaJS/issues/47) Implement an alert box. This extends the modal plugin and uses the card style. The type of alert box can be specified, adjusting the colours used. | |||
| + **Tweak** [#61](https://github.com/VizuaaLOG/BulmaJS/pull/61) Remove the assumed window assignment, if you rely on this then you'll need to ensure you add this to you applications JS. | |||
| + **Tweak BREAKING** The modal's `type` attribute has been renamed to `style`. This value is changing the 'style' of the modal, and so this better reflects the option. | |||
| + **Bug** [#61](https://github.com/VizuaaLOG/BulmaJS/pull/61) Add a default export for the full BulmaJS bundle. | |||
| + **Deprecated** Wikiki's Accordion plugin has been Deprecated from the core and will be removed in the 1.0 release. If you are using Wikiki's Accordion extension then it's recommended to use the official JS integration for it. | |||
| + **Deprecated** Wikiki's Calendar plugin has been Deprecated from the core and will be removed from the 1.0 release. If you are using Wikiki's Calendar extension then it's recommended to use the official JS integration for it. | |||
| # 0.7.0 | |||
| + **Feature** [#27](https://github.com/VizuaaLOG/BulmaJS/issues/27) Modals can now be closed by pressing the | |||
| `escape` key. | |||
| + **Feature** [#27](https://github.com/VizuaaLOG/BulmaJS/issues/27) Modals can now be closed by clicking outside of an open modal. | |||
| + **Feature** [#27](https://github.com/VizuaaLOG/BulmaJS/issues/27) When a modal is opening the `is-clipped` class is applied to the `body` element. | |||
| + **Feature** Added a new `findElement` helper method to the BulmaJS core. This method will allow plugins to easily normalise an arugment that needs to be either an HTMLElement or a query selector string. | |||
| + **Feature** [#27](https://github.com/VizuaaLOG/BulmaJS/issues/27) Rewrite the modal plugin, providing a new functionality, options and a full Javascript API. | |||
| + **Feature** [#50](https://github.com/VizuaaLOG/BulmaJS/issues/50) Allow multiple plugins to be attached to a single class. | |||
| + **Feature** [#39](https://github.com/VizuaaLOG/BulmaJS/issues/39) Implement a uniformed plugin interface | |||
| + **Feature** [#40](https://github.com/VizuaaLOG/BulmaJS/issues/49) Add the ability for the navbar to be sticky at a certain threshold. Also add the ability for the navbar to hide on scroll down and reappear on scroll up. Thanks to [rdhar](https://github.com/rdhar) for the suggestion! | |||
| + **Tweak** [#51](https://github.com/VizuaaLOG/BulmaJS/pull/51) Thanks to [apecell](https://github.com/apecell) the file plugin now supports drag and drop. | |||
| + **Bug** [#27](https://github.com/VizuaaLOG/BulmaJS/issues/27) `closeButton` event is now correctly removed when destroying a modal. | |||
| + **Bug** [#53](https://github.com/VizuaaLOG/BulmaJS/issues/53) The tabs plugin now ensures it only grabs the ul/li element that is a direct child of the `tabs-content` element. | |||
| + **Bug** [#54](https://github.com/VizuaaLOG/BulmaJS/issues/54) Fix an issue where plugins were sometimes attached twice. | |||
| # 0.6.0 | |||
| The reason for this being a large version jump is due to the complete overhaul of the documentation. While this doesn't change the functionality of the code, I feel like this is a large enough change to the project to warrant more than a 'patch' version bump. | |||
| + [#36](https://github.com/VizuaaLOG/BulmaJS/issues/36) The navbar burger button now has the `is-active` class toggled. | |||
| + [#22](https://github.com/VizuaaLOG/BulmaJS/issues/22) Documentation has had a redesign and a complete rewrite. If you find a typo please blame my keyboard ( :) ) and file an issue or submit a pull request. | |||
| # 0.5.0 | |||
| + [#38](https://github.com/VizuaaLOG/BulmaJS/pull/38) Adjust how plugins are initialised by using classes instead. Data attributes can still be used for customising the plugins behaviour, if supported. (closes [#20](https://github.com/VizuaaLOG/BulmaJS/issues/20)) | |||
| + [43b64cd](https://github.com/VizuaaLOG/BulmaJS/commit/43b64cdea58fe6b512ce95c69172889d75b68179) Add a new option to the tabs plugin that allows tabs to be changed when the user hovers over the tab link. On mobile this will revert back to the click/tap handler due to the lack of a hover event being called. (closes [#35](https://github.com/VizuaaLOG/BulmaJS/issues/35)) | |||
| # 0.4.0 | |||
| + [#28](https://github.com/VizuaaLOG/BulmaJS/pull/28) Add the option to disable the modal being closable | |||
| + [#31](https://github.com/VizuaaLOG/BulmaJS/pull/31) Add automated linting to the CI process (closes [#20](https://github.com/VizuaaLOG/BulmaJS/issues/29)) | |||
| # 0.3.1 | |||
| + This changelog was added | |||
| + Fix Wikiki's CSS files not being loaded on the docs | |||
| + Publish to NPM | |||
| @ -0,0 +1,111 @@ | |||
| (function webpackUniversalModuleDefinition(root, factory) { | |||
| if(typeof exports === 'object' && typeof module === 'object') | |||
| module.exports = factory(); | |||
| else if(typeof define === 'function' && define.amd) | |||
| define("Bulma", [], factory); | |||
| else if(typeof exports === 'object') | |||
| exports["Bulma"] = factory(); | |||
| else | |||
| root["Bulma"] = factory(); | |||
| })(window, function() { | |||
| return /******/ (function(modules) { // webpackBootstrap | |||
| /******/ // The module cache | |||
| /******/ var installedModules = {}; | |||
| /******/ | |||
| /******/ // The require function | |||
| /******/ function __webpack_require__(moduleId) { | |||
| /******/ | |||
| /******/ // Check if module is in cache | |||
| /******/ if(installedModules[moduleId]) { | |||
| /******/ return installedModules[moduleId].exports; | |||
| /******/ } | |||
| /******/ // Create a new module (and put it into the cache) | |||
| /******/ var module = installedModules[moduleId] = { | |||
| /******/ i: moduleId, | |||
| /******/ l: false, | |||
| /******/ exports: {} | |||
| /******/ }; | |||
| /******/ | |||
| /******/ // Execute the module function | |||
| /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); | |||
| /******/ | |||
| /******/ // Flag the module as loaded | |||
| /******/ module.l = true; | |||
| /******/ | |||
| /******/ // Return the exports of the module | |||
| /******/ return module.exports; | |||
| /******/ } | |||
| /******/ | |||
| /******/ | |||
| /******/ // expose the modules object (__webpack_modules__) | |||
| /******/ __webpack_require__.m = modules; | |||
| /******/ | |||
| /******/ // expose the module cache | |||
| /******/ __webpack_require__.c = installedModules; | |||
| /******/ | |||
| /******/ // define getter function for harmony exports | |||
| /******/ __webpack_require__.d = function(exports, name, getter) { | |||
| /******/ if(!__webpack_require__.o(exports, name)) { | |||
| /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); | |||
| /******/ } | |||
| /******/ }; | |||
| /******/ | |||
| /******/ // define __esModule on exports | |||
| /******/ __webpack_require__.r = function(exports) { | |||
| /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { | |||
| /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | |||
| /******/ } | |||
| /******/ Object.defineProperty(exports, '__esModule', { value: true }); | |||
| /******/ }; | |||
| /******/ | |||
| /******/ // create a fake namespace object | |||
| /******/ // mode & 1: value is a module id, require it | |||
| /******/ // mode & 2: merge all properties of value into the ns | |||
| /******/ // mode & 4: return value when already ns object | |||
| /******/ // mode & 8|1: behave like require | |||
| /******/ __webpack_require__.t = function(value, mode) { | |||
| /******/ if(mode & 1) value = __webpack_require__(value); | |||
| /******/ if(mode & 8) return value; | |||
| /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; | |||
| /******/ var ns = Object.create(null); | |||
| /******/ __webpack_require__.r(ns); | |||
| /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); | |||
| /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); | |||
| /******/ return ns; | |||
| /******/ }; | |||
| /******/ | |||
| /******/ // getDefaultExport function for compatibility with non-harmony modules | |||
| /******/ __webpack_require__.n = function(module) { | |||
| /******/ var getter = module && module.__esModule ? | |||
| /******/ function getDefault() { return module['default']; } : | |||
| /******/ function getModuleExports() { return module; }; | |||
| /******/ __webpack_require__.d(getter, 'a', getter); | |||
| /******/ return getter; | |||
| /******/ }; | |||
| /******/ | |||
| /******/ // Object.prototype.hasOwnProperty.call | |||
| /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; | |||
| /******/ | |||
| /******/ // __webpack_public_path__ | |||
| /******/ __webpack_require__.p = ""; | |||
| /******/ | |||
| /******/ | |||
| /******/ // Load entry module and return exports | |||
| /******/ return __webpack_require__(__webpack_require__.s = "./src/plugins/panel.js"); | |||
| /******/ }) | |||
| /************************************************************************/ | |||
| /******/ ({ | |||
| /***/ "./src/plugins/panel.js": | |||
| /*!******************************!*\ | |||
| !*** ./src/plugins/panel.js ***! | |||
| \******************************/ | |||
| /*! no static exports found */ | |||
| /***/ (function(module, exports) { | |||
| eval("throw new Error(\"Module build failed (from ./node_modules/babel-loader/lib/index.js):\\nError: ENOENT: no such file or directory, open 'C:\\\\laragon\\\\www\\\\BulmaJS\\\\src\\\\plugins\\\\panel.js'\");\n\n//# sourceURL=webpack://Bulma/./src/plugins/panel.js?"); | |||
| /***/ }) | |||
| /******/ })["default"]; | |||
| }); | |||
| @ -0,0 +1,35 @@ | |||
| { | |||
| "name": "@vizuaalog/bulmajs", | |||
| "version": "0.12.2", | |||
| "description": "Unofficial javascript extension to the awesome Bulma CSS framework.", | |||
| "main": "src/bulma.js", | |||
| "author": "Thomas Erbe <vizuaalog@gmail.com>", | |||
| "license": "MIT", | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "git+https://github.com/VizuaaLOG/BulmaJS.git" | |||
| }, | |||
| "bugs": { | |||
| "url": "https://github.com/VizuaaLOG/BulmaJS/issues" | |||
| }, | |||
| "homepage": "https://github.com/VizuaaLOG/BulmaJS", | |||
| "devDependencies": { | |||
| "babel-core": "^6.26.0", | |||
| "babel-loader": "^7.1.4", | |||
| "babel-plugin-transform-object-rest-spread": "^6.26.0", | |||
| "babel-preset-env": "^1.6.1", | |||
| "bulma": "^0.7.1", | |||
| "eslint": "^4.19.0", | |||
| "node-qunit-puppeteer": "^1.0.12", | |||
| "webpack": "^4.20.0", | |||
| "webpack-cli": "^3.1.1", | |||
| "webpack-glob-entry": "^2.1.1" | |||
| }, | |||
| "scripts": { | |||
| "lint": "eslint 'src/**'", | |||
| "dev": "webpack --config webpack.config.js --mode development", | |||
| "production": "webpack --config webpack.config.js --mode production", | |||
| "watch": "webpack --config webpack.config.js --mode development --watch", | |||
| "test": "node-qunit-puppeteer ./tests/test-runner.html 10000 '--allow-file-access-from-files --no-sandbox'" | |||
| } | |||
| } | |||
| @ -0,0 +1,59 @@ | |||
| /** | |||
| * Object to hold a plugin's configuration | |||
| * @class ConfigBag | |||
| * @since 0.11.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export default class ConfigBag { | |||
| constructor(initialConfig = []) { | |||
| if(typeof initialConfig !== 'object') { | |||
| throw new TypeError('initialConfig must be of type object.'); | |||
| } | |||
| this._items = initialConfig; | |||
| } | |||
| /** | |||
| * Set a new config property | |||
| * @param {string} key The config property's key | |||
| * @param {mixed} value The config property's value | |||
| */ | |||
| set(key, value) { | |||
| if(!key || !value) { | |||
| throw new Error('A key and value must be provided when setting a new option.'); | |||
| } | |||
| this._items[key] = value; | |||
| } | |||
| /** | |||
| * Check if a key exists | |||
| * @param {string} key | |||
| * @returns {boolean} | |||
| */ | |||
| has(key) { | |||
| if(!key) { | |||
| throw new Error('A key must be provided.'); | |||
| } | |||
| return (this._items.hasOwnProperty(key) && this._items[key]); | |||
| } | |||
| /** | |||
| * Get a property by it's key. Returns the defaultValue if it doesn't exists | |||
| * @param {string} key | |||
| * @param {mixed} defaultValue | |||
| * @returns {mixed} | |||
| */ | |||
| get(key, defaultValue = null) { | |||
| if(defaultValue && !this.has(key)) { | |||
| if(typeof defaultValue === 'function') { | |||
| return defaultValue(); | |||
| } | |||
| return defaultValue; | |||
| } | |||
| return this._items[key]; | |||
| } | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| class Data { | |||
| constructor() { | |||
| this._data = {}; | |||
| } | |||
| set(uid, key, value) { | |||
| if(!this._data.hasOwnProperty(uid)) { | |||
| this._data[uid] = {}; | |||
| } | |||
| this._data[uid][key] = value; | |||
| } | |||
| get(uid, key) { | |||
| if(!this._data.hasOwnProperty(uid)) { | |||
| return undefined; | |||
| } | |||
| return this._data[uid][key]; | |||
| } | |||
| destroy(uid) { | |||
| if(this._data.hasOwnProperty(uid)) { | |||
| delete this._data[uid]; | |||
| } | |||
| } | |||
| } | |||
| Data.uid = 1; | |||
| export default Data; | |||
| @ -0,0 +1,14 @@ | |||
| /* eslint no-unused-vars: 0 */ | |||
| import Bulma from './core'; | |||
| import { Notification } from './plugins/notification'; | |||
| import { Navbar } from './plugins/navbar'; | |||
| import { Message } from './plugins/message'; | |||
| import { Dropdown } from './plugins/dropdown'; | |||
| import { Modal } from './plugins/modal'; | |||
| import { Alert } from './plugins/alert'; | |||
| import { File } from './plugins/file'; | |||
| import { Tabs } from './plugins/tabs'; | |||
| import { PanelTabs } from './plugins/panelTabs'; | |||
| export default Bulma; | |||
| @ -0,0 +1,288 @@ | |||
| import Data from './Data'; | |||
| /** | |||
| * Wrap an element around Bulma. | |||
| * @param {String|HTMLElement} selector The selector or HTMLElement to wrap. | |||
| */ | |||
| function Bulma(selector) { | |||
| if (!(this instanceof Bulma)) { | |||
| return new Bulma(selector); | |||
| } | |||
| if (selector instanceof Bulma) { | |||
| return selector; | |||
| } | |||
| if (selector instanceof HTMLElement) { | |||
| this._elem = selector; | |||
| } else { | |||
| this._elem = document.querySelector(selector); | |||
| } | |||
| if (!selector) { | |||
| this._elem = document.createElement('div'); | |||
| } | |||
| if (!this._elem.hasOwnProperty(Bulma.id)) { | |||
| this._elem[Bulma.id] = Data.uid++; | |||
| } | |||
| return this; | |||
| } | |||
| /** | |||
| * Current BulmaJS version. | |||
| * @type {String} | |||
| */ | |||
| Bulma.VERSION = '0.12.1'; | |||
| /** | |||
| * Unique ID of Bulma | |||
| * @type {String} | |||
| */ | |||
| Bulma.id = 'bulma-' + new Date().getTime(); | |||
| /** | |||
| * Global data cache for HTML elements | |||
| * @type {Data} | |||
| */ | |||
| Bulma.cache = new Data(); | |||
| /** | |||
| * An index of the registered plugins | |||
| * @type {Object} | |||
| */ | |||
| Bulma.plugins = {}; | |||
| /** | |||
| * Helper method to create a new plugin. | |||
| * @param {String} key The plugin's key | |||
| * @param {Object} config The config to be passed to the plugin | |||
| * @return {Object} The newly created plugin instance | |||
| */ | |||
| Bulma.create = (key, config) => { | |||
| if (!key || !Bulma.plugins.hasOwnProperty(key)) { | |||
| throw new Error('[BulmaJS] A plugin with the key \'' + key + '\' has not been registered.'); | |||
| } | |||
| return new Bulma.plugins[key].handler(config); | |||
| }; | |||
| /** | |||
| * Register a new plugin | |||
| * @param {String} key The key to register the plugin under | |||
| * @param {Object} plugin The plugin's main constructor | |||
| * @param {number?} priority The priority this plugin has over other plugins. Higher means the plugin is registered before lower. | |||
| * @return {undefined} | |||
| */ | |||
| Bulma.registerPlugin = (key, plugin, priority = 0) => { | |||
| if (!key) { | |||
| throw new Error('[BulmaJS] Key attribute is required.'); | |||
| } | |||
| Bulma.plugins[key] = { | |||
| priority: priority, | |||
| handler: plugin | |||
| }; | |||
| Bulma.prototype[key] = function (config) { | |||
| return new Bulma.plugins[key].handler(config, this); | |||
| }; | |||
| }; | |||
| /** | |||
| * Parse the HTML DOM searching for data-bulma attributes. We will then pass | |||
| * each element to the appropriate plugin to handle the required processing. | |||
| * @param {HTMLElement} root The root of the document we're going to parse. | |||
| * @return {undefined} | |||
| */ | |||
| Bulma.parseDocument = (root = document) => { | |||
| let sortedPlugins = Object.keys(Bulma.plugins) | |||
| .sort((a, b) => Bulma.plugins[a].priority < Bulma.plugins[b].priority); | |||
| Bulma.each(sortedPlugins, (key) => { | |||
| if (!Bulma.plugins[key].handler.hasOwnProperty('parseDocument')) { | |||
| // eslint-disable-next-line no-console | |||
| console.error('[BulmaJS] Plugin ' + key + ' does not have a parseDocument method. Automatic document parsing is not possible for this plugin.'); | |||
| return; | |||
| } | |||
| Bulma.plugins[key].handler.parseDocument(root); | |||
| }); | |||
| }; | |||
| /** | |||
| * Create an element and assign classes | |||
| * @param {string} name The name of the element to create | |||
| * @param {array} classes An array of classes to add to the element | |||
| * @return {HTMLElement} The newly created element | |||
| */ | |||
| Bulma.createElement = (name, classes) => { | |||
| if (!classes) { | |||
| classes = []; | |||
| } | |||
| if (typeof classes === 'string') { | |||
| classes = [classes]; | |||
| } | |||
| let elem = document.createElement(name); | |||
| Bulma.each(classes, (className) => { | |||
| elem.classList.add(className); | |||
| }); | |||
| return elem; | |||
| }; | |||
| /** | |||
| * Find an element otherwise create a new one. | |||
| * @param {string} query The CSS selector query to find | |||
| * @param {HTMLElement|null} parent The parent we want to search/create within | |||
| * @param {[string]} elemName The name of the element to create | |||
| * @param {[array]} classes The classes to apply to the element | |||
| * @returns {HTMLElement} The HTML element we found or created | |||
| */ | |||
| Bulma.findOrCreateElement = (query, parent = document, elemName = 'div', classes = []) => { | |||
| var elem = parent.querySelector(query); | |||
| if (!elem) { | |||
| if (classes.length === 0) { | |||
| classes = query.split('.').filter((item) => { | |||
| return item; | |||
| }); | |||
| } | |||
| var newElem = Bulma.createElement(elemName, classes); | |||
| parent.appendChild(newElem); | |||
| return newElem; | |||
| } | |||
| return elem; | |||
| }; | |||
| /** | |||
| * For loop helper | |||
| * @param {*} objects The array/object to loop through | |||
| * @param {*} callback The callback used for each item | |||
| */ | |||
| Bulma.each = (objects, callback) => { | |||
| let i; | |||
| for (i = 0; i < objects.length; i++) { | |||
| callback(objects[i], i); | |||
| } | |||
| }; | |||
| /** | |||
| * Make an AJAX GET request to the specified URL. Stripping any script tags from the response. | |||
| * @param {*} url The url to send the request to | |||
| * @returns {Promise} Returns a promise containing the response HTML or error | |||
| */ | |||
| Bulma.ajax = (url) => { | |||
| return new Promise((resolve, reject) => { | |||
| var request = new XMLHttpRequest(); | |||
| request.open('GET', url, true); | |||
| request.onload = () => { | |||
| if (request.status >= 200 && request.status < 400) { | |||
| resolve(Bulma._stripScripts(request.responseText)); | |||
| } else { | |||
| reject(); | |||
| } | |||
| }; | |||
| request.onerror = () => reject(); | |||
| request.send(); | |||
| }); | |||
| }; | |||
| /** | |||
| * Strip any script tags from a HTML string. | |||
| * @param {string} htmlString | |||
| * @returns {string} The cleaned HTML string | |||
| * | |||
| * @private | |||
| */ | |||
| Bulma._stripScripts = (htmlString) => { | |||
| var div = document.createElement('div'); | |||
| div.innerHTML = htmlString; | |||
| var scripts = div.getElementsByTagName('script'); | |||
| var i = scripts.length; | |||
| while (i--) { | |||
| scripts[i].parentNode.removeChild(scripts[i]); | |||
| } | |||
| return div.innerHTML.replace(/ +/g, ' '); | |||
| }; | |||
| /** | |||
| * Grabs a configuration property from the window.bulmaOptions global, if it's defined, | |||
| * returns defaultValue otherwise. | |||
| * @param {string} key The name of the option to check for | |||
| * @param {*} [defaultValue=null] A default value of the key is not found | |||
| */ | |||
| Bulma.getGlobalConfig = function (key, defaultValue = null) { | |||
| if (!window.hasOwnProperty('bulmaOptions')) { | |||
| return defaultValue; | |||
| } | |||
| if (!window.bulmaOptions.hasOwnProperty(key)) { | |||
| return defaultValue; | |||
| } | |||
| return window.bulmaOptions[key]; | |||
| }; | |||
| /** | |||
| * Get or set custom data on a Bulma element. | |||
| * @type {String} key | |||
| * @type {any} value | |||
| * @returns {Bulma} | |||
| */ | |||
| Bulma.prototype.data = function (key, value) { | |||
| if (!value) { | |||
| return Bulma.cache.get(this._elem[Bulma.id], key); | |||
| } | |||
| Bulma.cache.set(this._elem[Bulma.id], key, value); | |||
| return this; | |||
| }; | |||
| /** | |||
| * Destroy the data for an element. | |||
| * @returns {Bulma} | |||
| */ | |||
| Bulma.prototype.destroyData = function () { | |||
| Bulma.cache.destroy(this._elem[Bulma.id]); | |||
| return this; | |||
| }; | |||
| /** | |||
| * Returns the internal HTMLElement we're wrapping. | |||
| * | |||
| * @returns {HTMLElement} | |||
| */ | |||
| Bulma.prototype.getElement = function () { | |||
| return this._elem; | |||
| }; | |||
| document.addEventListener('DOMContentLoaded', () => { | |||
| if (Bulma.getGlobalConfig('autoParseDocument', true)) { | |||
| Bulma.parseDocument(); | |||
| } | |||
| if (Bulma.getGlobalConfig('onLoaded')) { | |||
| Bulma.getGlobalConfig('onLoaded')(); | |||
| } | |||
| }); | |||
| export default Bulma; | |||
| @ -0,0 +1,218 @@ | |||
| import Bulma from './core'; | |||
| import Plugin from './plugin'; | |||
| /** | |||
| * @module DismissableComponent | |||
| * @since 0.2.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export default class DismissableComponent extends Plugin { | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultConfig() { | |||
| return { | |||
| isDismissable: false, | |||
| destroyOnDismiss: true, | |||
| element: null | |||
| }; | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {string} name Plugin's name | |||
| * @param {Object} config Plugin's config | |||
| * @return {this} The new plugin instance | |||
| */ | |||
| constructor(name, config, root) { | |||
| if(!root._elem.classList.contains(name)) { | |||
| config['parent'] = root; | |||
| root = null; | |||
| } | |||
| super(config, root); | |||
| /** | |||
| * The name of this component, this will be used as the root class | |||
| * @type {string} | |||
| */ | |||
| this.name = name; | |||
| /** | |||
| * Body text. | |||
| * @type {string} | |||
| */ | |||
| this.body = this.config.get('body'); | |||
| /** | |||
| * Color modifier. | |||
| * @type {string} Possible values are null, primary, info, success, warning, danger | |||
| */ | |||
| this.color = this.config.get('color'); | |||
| /** | |||
| * How long to wait before auto dismissing the component. | |||
| * @type {int|null} If null component must be dismissed manually. | |||
| */ | |||
| this.dismissInterval = this.config.get('dismissInterval') ? this.createDismissInterval(this.config.get('dismissInterval')) : null; | |||
| /** | |||
| * Does this component have a dismiss button? | |||
| * @type {Boolean} | |||
| */ | |||
| this.isDismissable = this.config.get('isDismissable'); | |||
| /** | |||
| * Should this component be destroyed when it is dismissed. | |||
| * @type {Boolean} | |||
| */ | |||
| this.destroyOnDismiss = this.config.get('destroyOnDismiss'); | |||
| // TODO: Make internal element references all be a Bulma instance. This will keep consistency. | |||
| if(!(this.parent instanceof Bulma)) { | |||
| this.parent = Bulma(this.parent); | |||
| } | |||
| /** | |||
| * The root element. | |||
| * @type {HTMLElement|null} If this is not provided a new element will be created. | |||
| */ | |||
| this.root = this.config.get('root', this.createRootElement.bind(this)); | |||
| /** | |||
| * The element used to close the component. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.closeButton = this.config.get('closeButton', this.createCloseButton()); | |||
| if(this.body) { | |||
| this.insertBody(); | |||
| } | |||
| if(this.color) { | |||
| this.setColor(); | |||
| } | |||
| } | |||
| /** | |||
| * Create the main element. | |||
| * @return {HTMLElement} | |||
| */ | |||
| createRootElement() { | |||
| let elem = document.createElement('div'); | |||
| elem.classList.add(this.name, 'is-hidden'); | |||
| elem.setAttribute('data-bulma-attached', 'attached'); | |||
| this.parent.getElement().appendChild(elem); | |||
| return elem; | |||
| } | |||
| /** | |||
| * Show the component. | |||
| * @return {undefined} | |||
| */ | |||
| show() { | |||
| this.root.classList.remove('is-hidden'); | |||
| } | |||
| /** | |||
| * Hide the component. | |||
| * @return {undefined} | |||
| */ | |||
| hide() { | |||
| this.root.classList.add('is-hidden'); | |||
| } | |||
| /** | |||
| * Insert the body text into the component. | |||
| * @return {undefined} | |||
| */ | |||
| insertBody() { | |||
| this.root.innerHTML = this.body; | |||
| } | |||
| /** | |||
| * Create the element that will be used to close the component. | |||
| * @return {HTMLElement} The newly created close button | |||
| */ | |||
| createCloseButton() { | |||
| var closeButton = document.createElement('button'); | |||
| closeButton.setAttribute('type', 'button'); | |||
| closeButton.classList.add('delete'); | |||
| return closeButton; | |||
| } | |||
| /** | |||
| * Create an interval to dismiss the component after the set number of ms. | |||
| * @param {int} interval The time to wait before dismissing the component | |||
| * @return {undefined} | |||
| */ | |||
| createDismissInterval(interval) { | |||
| return setInterval(() => { | |||
| this.handleCloseEvent(); | |||
| }, interval); | |||
| } | |||
| /** | |||
| * Insert the close button before our content. | |||
| * @return {undefined} | |||
| */ | |||
| prependCloseButton() { | |||
| this.root.insertBefore(this.closeButton, this.root.firstChild); | |||
| } | |||
| /** | |||
| * Setup the event listener for the close button. | |||
| * @return {undefined} | |||
| */ | |||
| setupCloseEvent() { | |||
| this.closeButton.addEventListener('click', this.handleCloseEvent.bind(this)); | |||
| } | |||
| /** | |||
| * Handle the event when our close button is clicked. | |||
| * @return {undefined} | |||
| */ | |||
| handleCloseEvent() { | |||
| this.trigger('dismissed'); | |||
| if(this.destroyOnDismiss) { | |||
| this.destroy(); | |||
| } else { | |||
| this.hide(); | |||
| } | |||
| this.trigger('close'); | |||
| } | |||
| /** | |||
| * Set the colour of the component. | |||
| * @return {undefined} | |||
| */ | |||
| setColor() { | |||
| this.root.classList.add('is-' + this.color); | |||
| } | |||
| /** | |||
| * Destroy the component, removing the event listener, interval and element. | |||
| * @return {undefined} | |||
| */ | |||
| destroy() { | |||
| super.destroy(); | |||
| if(this.closeButton) { | |||
| this.closeButton.removeEventListener('click', this.handleCloseEvent.bind(this)); | |||
| } | |||
| clearInterval(this.dismissInterval); | |||
| this.parent.getElement().removeChild(this.root); | |||
| this.parent = null; | |||
| this.root = null; | |||
| this.trigger('destroyed'); | |||
| } | |||
| } | |||
| @ -0,0 +1,58 @@ | |||
| import ConfigBag from './ConfigBag'; | |||
| import Bulma from './core'; | |||
| /** | |||
| * Base plugin class. Provides basic, common functionality. | |||
| * @class Plugin | |||
| * @since 0.7.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export default class Plugin { | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultConfig() { | |||
| return {}; | |||
| } | |||
| /** | |||
| * Create a plugin. | |||
| * @param {object} config The config for this plugin | |||
| */ | |||
| constructor(config = {}, root) { | |||
| config.root = (root instanceof Bulma) ? root._elem : root; | |||
| this.config = new ConfigBag({...this.constructor.defaultConfig(), ...config}); | |||
| if(!root && !this.config.has('parent')) { | |||
| throw new Error('A plugin requires a root and/or a parent.'); | |||
| } | |||
| this.parent = this.config.get('parent', config.root ? config.root.parentNode : null); | |||
| this._events = {}; | |||
| } | |||
| on(event, callback) { | |||
| if(!this._events.hasOwnProperty(event)) { | |||
| this._events[event] = []; | |||
| } | |||
| this._events[event].push(callback); | |||
| } | |||
| trigger(event, data = {}) { | |||
| if(!this._events.hasOwnProperty(event)) { | |||
| return; | |||
| } | |||
| for(let i = 0; i < this._events[event].length; i++) { | |||
| this._events[event][i](data); | |||
| } | |||
| } | |||
| destroy() { | |||
| Bulma(this.root).destroyData(); | |||
| } | |||
| } | |||
| @ -0,0 +1,138 @@ | |||
| import Bulma from '../core'; | |||
| import { Modal } from './modal'; | |||
| /** | |||
| * @module Alert | |||
| * @since 0.8.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class Alert extends Modal { | |||
| /** | |||
| * Handle parsing the DOM. | |||
| * @param {HTMLElement} element The root element for this accordion | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument() {} | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultConfig() { | |||
| return { | |||
| type: 'info', | |||
| title: '', | |||
| body: '', | |||
| confirm: 'Okay', | |||
| cancel: null, | |||
| style: 'card', | |||
| parent: document.body, | |||
| showHeader: true | |||
| }; | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created plugin instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| this.root.classList.add('alert'); | |||
| Bulma(this.root).data('alert', this); | |||
| this.trigger('init'); | |||
| this.open(); | |||
| } | |||
| /** | |||
| * Create the alerts structure | |||
| * @returns {void} | |||
| */ | |||
| createCardStructure() { | |||
| if(this.config.get('showHeader')) { | |||
| /** @param {HTMLElement} */ | |||
| this.header = Bulma.findOrCreateElement('.modal-card-head', this.content, 'header', ['modal-card-head', 'has-background-' + this.config.get('type')]); | |||
| /** @param {HTMLElement} */ | |||
| var textColor = this.config.get('type') == 'warning' ? 'black' : 'white'; | |||
| this.headerTitle = Bulma.createElement('p', ['modal-card-title', 'has-text-' + textColor]); | |||
| this.headerTitle.innerHTML = this.title; | |||
| this.header.appendChild(this.headerTitle); | |||
| } | |||
| /** @param {HTMLElement} */ | |||
| this.cardBody = Bulma.findOrCreateElement('.modal-card-body', this.content, 'section'); | |||
| if(!this.cardBody.innerHTML) { | |||
| this.cardBody.innerHTML = this.body; | |||
| } | |||
| /** @param {HTMLElement} */ | |||
| this.footer = Bulma.findOrCreateElement('.modal-card-foot', this.content, 'footer'); | |||
| } | |||
| /** | |||
| * Go through the provided buttons option and create the buttons. | |||
| * @returns {void} | |||
| */ | |||
| createButtons() { | |||
| var defaultButtonOptions = { close: true, destroy: true, onClick: function() {} }; | |||
| var confirmOptions = this.config.get('confirm'); | |||
| if(typeof confirmOptions === 'string') { | |||
| confirmOptions = { | |||
| label: confirmOptions, | |||
| classes: [] | |||
| }; | |||
| } | |||
| confirmOptions = { ...defaultButtonOptions, ...confirmOptions}; | |||
| var confirmButton = Bulma.createElement('button', ['button', 'is-' + this.config.get('type')].concat(confirmOptions.classes)); | |||
| confirmButton.innerHTML = confirmOptions.label; | |||
| confirmButton.addEventListener('click', e => { | |||
| confirmOptions.onClick(e); | |||
| if(confirmOptions.close) { | |||
| this.close(); | |||
| } | |||
| if(confirmOptions.destroy) { | |||
| this.destroy(); | |||
| } | |||
| }); | |||
| this.footer.appendChild(confirmButton); | |||
| if(this.config.get('cancel')) { | |||
| var cancelOptions = this.config.get('cancel'); | |||
| if(typeof cancelOptions === 'string') { | |||
| cancelOptions = { | |||
| label: cancelOptions, | |||
| classes: [] | |||
| }; | |||
| } | |||
| cancelOptions = { ...defaultButtonOptions, ...cancelOptions}; | |||
| var cancelButton = Bulma.createElement('button', ['button'].concat(cancelOptions.classes)); | |||
| cancelButton.innerHTML = cancelOptions.label; | |||
| cancelButton.addEventListener('click', e => { | |||
| cancelOptions.onClick(e); | |||
| if(cancelOptions.close) { | |||
| this.close(); | |||
| } | |||
| if(cancelOptions.destroy) { | |||
| this.destroy(); | |||
| } | |||
| }); | |||
| this.footer.appendChild(cancelButton); | |||
| } | |||
| } | |||
| } | |||
| Bulma.registerPlugin('alert', Alert); | |||
| export default Bulma; | |||
| @ -0,0 +1,84 @@ | |||
| import Bulma from '../core'; | |||
| import Plugin from '../plugin'; | |||
| /** | |||
| * @module Dropdown | |||
| * @since 0.1.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class Dropdown extends Plugin { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HtmlElement} element The root element for this instance | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.contains('dropdown')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.dropdown'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| Bulma(element).dropdown(); | |||
| }); | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| /** | |||
| * The root dropdown element. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.root = this.config.get('root'); | |||
| this.root.setAttribute('data-bulma-attached', 'attached'); | |||
| /** | |||
| * The element to trigger when clicked. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.triggerElement = this.root.querySelector('.dropdown-trigger'); | |||
| this.registerEvents(); | |||
| Bulma(this.root).data('dropdown', this); | |||
| this.trigger('init'); | |||
| } | |||
| /** | |||
| * Register all the events this module needs. | |||
| * @return {undefined} | |||
| */ | |||
| registerEvents() { | |||
| this.triggerElement.addEventListener('click', this.handleTriggerClick.bind(this)); | |||
| } | |||
| /** | |||
| * Handle the click event on the trigger. | |||
| * @return {undefined} | |||
| */ | |||
| handleTriggerClick() { | |||
| if (this.root.classList.contains('is-active')) { | |||
| this.root.classList.remove('is-active'); | |||
| this.trigger('close'); | |||
| } else { | |||
| this.root.classList.add('is-active'); | |||
| this.trigger('open'); | |||
| } | |||
| } | |||
| } | |||
| Bulma.registerPlugin('dropdown', Dropdown); | |||
| export default Bulma; | |||
| @ -0,0 +1,155 @@ | |||
| import Bulma from '../core'; | |||
| import Plugin from '../plugin'; | |||
| /** | |||
| * @module File | |||
| * @since 0.1.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class File extends Plugin { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HTMLElement} element The root element for this plugin | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.contains('file')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.file'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| Bulma(element).file(); | |||
| }); | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created plugin instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| /** | |||
| * The root file element. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.root = this.config.get('root'); | |||
| this.root.setAttribute('data-bulma-attached', 'attached'); | |||
| /** | |||
| * The element to use as the trigger. | |||
| * @type {HTMLELement} | |||
| */ | |||
| this.input = this.root.querySelector('input'); | |||
| /** | |||
| * The element to show the file name. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.filename = this.root.querySelector('.file-name'); | |||
| this.registerEvents(); | |||
| Bulma(this.root).data('file', this); | |||
| this.trigger('init'); | |||
| } | |||
| /** | |||
| * Register all the events this module needs. | |||
| * @return {undefined} | |||
| */ | |||
| registerEvents() { | |||
| if (this.filename) { | |||
| this.input.addEventListener('change', this.handleTriggerChange.bind(this)); | |||
| } | |||
| this.root.addEventListener('dragover', (e) => { | |||
| e.preventDefault(); | |||
| this.addHoverClass(); | |||
| }); | |||
| this.root.addEventListener('dragleave', (e) => { | |||
| e.preventDefault(); | |||
| this.addHoverClass(); | |||
| }); | |||
| this.root.addEventListener('drop', (e) => { | |||
| e.preventDefault(); | |||
| this.removeHoverClass(); | |||
| this.input.files = e.dataTransfer.files; | |||
| }); | |||
| } | |||
| /** | |||
| * Handle the click event on the trigger. | |||
| * @param {Object} event The event object | |||
| * @return {undefined} | |||
| */ | |||
| handleTriggerChange(event) { | |||
| if (event.target.files.length === 0) { | |||
| this.clearFileName(); | |||
| } | |||
| if (event.target.files.length === 1) { | |||
| this.setFileName(event.target.files[0].name); | |||
| } | |||
| if (event.target.files.length > 1) { | |||
| this.setFileName(event.target.files.length + ' files'); | |||
| } | |||
| this.trigger('changed', event); | |||
| } | |||
| /** | |||
| * Clear the file name element. | |||
| * @return {undefined} | |||
| */ | |||
| clearFileName() { | |||
| this.filename.innerHTML = ''; | |||
| } | |||
| /** | |||
| * Get the selected file's name | |||
| * | |||
| * @returns {string} | |||
| */ | |||
| getFilename() { | |||
| return this.filename.innerHTML; | |||
| } | |||
| /** | |||
| * Set the text for the file name element. | |||
| * @param {string} value The name of the file to update the label with | |||
| * @return {undefined} | |||
| */ | |||
| setFileName(value) { | |||
| this.filename.innerHTML = value; | |||
| } | |||
| /** | |||
| * Add hover class to root element. | |||
| * @return {undefined} | |||
| */ | |||
| addHoverClass() { | |||
| this.root.classList.add('is-hovered'); | |||
| } | |||
| /** | |||
| * Remove hover class from root element. | |||
| * @return {undefined} | |||
| */ | |||
| removeHoverClass() { | |||
| this.root.classList.remove('is-hovered'); | |||
| } | |||
| } | |||
| Bulma.registerPlugin('file', File); | |||
| export default Bulma; | |||
| @ -0,0 +1,128 @@ | |||
| import Bulma from '../core'; | |||
| import DismissableComponent from '../dismissableComponent'; | |||
| /** | |||
| * @module Message | |||
| * @since 0.1.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| * @extends DismissableComponent | |||
| */ | |||
| export class Message extends DismissableComponent { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HTMLElement} element The root element for this plugin | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.container('.message')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.message'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| let closeBtn = element.querySelector('.delete'); | |||
| Bulma(element).message({ | |||
| body: null, | |||
| closeButton: closeBtn, | |||
| isDismissable: !!closeBtn, | |||
| destroyOnDismiss: true, | |||
| dismissInterval: element.hasAttribute('data-dismiss-interval') ? element.getAttribute('data-dismiss-interval') : null | |||
| }); | |||
| }); | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created instance | |||
| */ | |||
| constructor(config, root) { | |||
| super('message', config, root); | |||
| /** | |||
| * The size of the message | |||
| * @type {String} Possible values are small, normal, medium or large | |||
| */ | |||
| this.size = this.config.get('size'); | |||
| /** | |||
| * The title of the message | |||
| * @type {String} | |||
| */ | |||
| this.title = this.config.get('title'); | |||
| if (this.title) { | |||
| this.createMessageHeader(); | |||
| } | |||
| // TODO: Move this into the DismissableComponent class. Due to the required | |||
| // changes between different components, we may need a way to trigger this | |||
| // when the component is ready. | |||
| if (this.isDismissable) { | |||
| if (!this.config.get('closeButton')) { | |||
| this.prependCloseButton(); | |||
| } | |||
| this.setupCloseEvent(); | |||
| } | |||
| if (this.size) { | |||
| this.setSize(); | |||
| } | |||
| Bulma(this.root).data('message', this); | |||
| this.trigger('init'); | |||
| } | |||
| /** | |||
| * Create the message header | |||
| * @return {undefined} | |||
| */ | |||
| createMessageHeader() { | |||
| let header = document.createElement('div'); | |||
| header.classList.add('message-header'); | |||
| header.innerHTML = '<p>' + this.title + '</p>'; | |||
| this.title = header; | |||
| this.root.insertBefore(this.title, this.root.firstChild); | |||
| } | |||
| /** | |||
| * Set the size of the message. | |||
| * @return {undefined} | |||
| */ | |||
| setSize() { | |||
| this.root.classList.add('is-' + this.size); | |||
| } | |||
| /** | |||
| * Insert the body text into the component. | |||
| * @return {undefined} | |||
| */ | |||
| insertBody() { | |||
| let body = document.createElement('div'); | |||
| body.classList.add('message-body'); | |||
| body.innerHTML = this.body; | |||
| this.root.appendChild(body); | |||
| } | |||
| /** | |||
| * Insert the close button before our content. | |||
| * @return {undefined} | |||
| */ | |||
| prependCloseButton() { | |||
| this.title.appendChild(this.closeButton); | |||
| } | |||
| } | |||
| Bulma.registerPlugin('message', Message); | |||
| export default Bulma; | |||
| @ -0,0 +1,238 @@ | |||
| import Bulma from '../core'; | |||
| import Plugin from '../plugin'; | |||
| /** | |||
| * @module Modal | |||
| * @since 0.1.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class Modal extends Plugin { | |||
| /** | |||
| * Handle parsing the DOM. | |||
| * @param {HTMLElement} element The root element for this accordion | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument() {} | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultConfig() { | |||
| return { | |||
| style: 'card', | |||
| closable: true | |||
| }; | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created plugin instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| /** @param {string} */ | |||
| this.style = this.config.get('style'); | |||
| /** @param {HTMLElement} */ | |||
| this.root = this.config.get('root'); | |||
| if(!this.root.classList.contains('modal')) { | |||
| this.root.classList.add('modal'); | |||
| } | |||
| if(!this.parent) { | |||
| if(!this.root.parentNode) { | |||
| this.parent = document.body; | |||
| this.parent.appendChild(this.root); | |||
| } else { | |||
| this.parent = this.root.parentNode; | |||
| } | |||
| } else { | |||
| this.parent.appendChild(this.root); | |||
| } | |||
| /** @param {HTMLElement} */ | |||
| this.background = Bulma.findOrCreateElement('.modal-background', this.root); | |||
| /** @param {HTMLElement} */ | |||
| this.content = this.style === 'card' ? Bulma.findOrCreateElement('.modal-card', this.root) : Bulma.findOrCreateElement('.modal-content', this.root); | |||
| /** @param {boolean} */ | |||
| this.closable = this.config.get('closable'); | |||
| /** @param {string|null} */ | |||
| this.body = this.config.get('body'); | |||
| /** @param {string|null} */ | |||
| this.title = this.config.get('title'); | |||
| if(this.config.get('bodyUrl')) { | |||
| Bulma.ajax(this.config.get('bodyUrl')) | |||
| .then((response) => { | |||
| this.body = response; | |||
| this.buildModal(); | |||
| }); | |||
| } else { | |||
| this.buildModal(); | |||
| } | |||
| Bulma(this.root).data('modal', this); | |||
| this.trigger('init'); | |||
| } | |||
| // Build the modal's HTML | |||
| buildModal() { | |||
| if(this.style === 'card') { | |||
| this.createCardStructure(); | |||
| } else { | |||
| if(!this.content.innerHTML) { | |||
| this.content.innerHTML = this.body; | |||
| } | |||
| } | |||
| if(this.closable) { | |||
| /** @param {HTMLElement} */ | |||
| this.closeButton = this.style === 'card' ? Bulma.findOrCreateElement('.delete', this.header, 'button') : Bulma.findOrCreateElement('.modal-close', this.root, 'button'); | |||
| } | |||
| if(this.style === 'card') { | |||
| this.createButtons(); | |||
| } | |||
| this.setupEvents(); | |||
| } | |||
| /** | |||
| * Create the card style structure | |||
| * @returns {void} | |||
| */ | |||
| createCardStructure() { | |||
| /** @param {HTMLElement} */ | |||
| this.header = Bulma.findOrCreateElement('.modal-card-head', this.content, 'header'); | |||
| /** @param {HTMLElement} */ | |||
| this.headerTitle = Bulma.findOrCreateElement('.modal-card-title', this.header, 'p'); | |||
| if(!this.headerTitle.innerHTML) { | |||
| this.headerTitle.innerHTML = this.title; | |||
| } | |||
| /** @param {HTMLElement} */ | |||
| this.cardBody = Bulma.findOrCreateElement('.modal-card-body', this.content, 'section'); | |||
| if(!this.cardBody.innerHTML) { | |||
| this.cardBody.innerHTML = this.body; | |||
| } | |||
| /** @param {HTMLElement} */ | |||
| this.footer = Bulma.findOrCreateElement('.modal-card-foot', this.content, 'footer'); | |||
| } | |||
| /** | |||
| * Setup the events used by this modal. | |||
| * @returns {void} | |||
| */ | |||
| setupEvents() { | |||
| if(this.closable) { | |||
| this.closeButton.addEventListener('click', this.close.bind(this)); | |||
| this.keyupListenerBound = (evt) => this.keyupListener(evt); | |||
| document.addEventListener('keyup', this.keyupListenerBound); | |||
| this.background.addEventListener('click', this.close.bind(this)); | |||
| } | |||
| } | |||
| /** | |||
| * Go through the provided buttons option and create the buttons. | |||
| * @returns {void} | |||
| */ | |||
| createButtons() { | |||
| var buttonsConfig = this.config.get('buttons', []); | |||
| var modal = this; | |||
| Bulma.each(buttonsConfig, function(buttonConfig) { | |||
| var button = Bulma.createElement('button', buttonConfig.classes); | |||
| button.innerHTML = buttonConfig.label; | |||
| button.addEventListener('click', function(event) { | |||
| buttonConfig.onClick(event); | |||
| }); | |||
| modal.footer.appendChild(button); | |||
| }); | |||
| } | |||
| /** | |||
| * Open the modal | |||
| * @returns {void} | |||
| */ | |||
| open() { | |||
| this.root.classList.add('is-active'); | |||
| document.documentElement.classList.add('is-clipped'); | |||
| this.trigger('open'); | |||
| } | |||
| /** | |||
| * Close the modal | |||
| * @returns {void} | |||
| */ | |||
| close() { | |||
| this.root.classList.remove('is-active'); | |||
| document.documentElement.classList.remove('is-clipped'); | |||
| this.trigger('close'); | |||
| } | |||
| keyupListener(event) { | |||
| if(!this.root.classList.contains('is-active')) { | |||
| return; | |||
| } | |||
| let key = event.key || event.keyCode; | |||
| if(key === 'Escape' || key === 'Esc' || key === 27) { | |||
| this.close(); | |||
| } | |||
| } | |||
| /** | |||
| * Destroy this modal, unregistering element references and removing the modal. | |||
| * @returns {void} | |||
| */ | |||
| destroy() { | |||
| super.destroy(); | |||
| this.root.remove(); | |||
| this.parent = null; | |||
| this.root = null; | |||
| this.background = null; | |||
| this.content = null; | |||
| if(this.style === 'card') { | |||
| this.header = null; | |||
| this.headerTitle = null; | |||
| this.cardBody = null; | |||
| this.footer = null; | |||
| } | |||
| if(this.closable) { | |||
| this.closeButton = null; | |||
| document.removeEventListener('keyup', this.keyupListenerBound); | |||
| } | |||
| this.config.gets = []; | |||
| this.trigger('destroyed'); | |||
| } | |||
| } | |||
| Bulma.registerPlugin('modal', Modal); | |||
| export default Bulma; | |||
| @ -0,0 +1,290 @@ | |||
| import Bulma from '../core'; | |||
| import Plugin from '../plugin'; | |||
| /** | |||
| * @module Navbar | |||
| * @since 0.1.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class Navbar extends Plugin { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HTMLElement} element The root element for this instance | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.contains('navbar')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.navbar'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| Bulma(element).navbar({ | |||
| sticky: element.hasAttribute('data-sticky') ? true : false, | |||
| stickyOffset: element.hasAttribute('data-sticky-offset') ? element.getAttribute('data-sticky-offset') : 0, | |||
| hideOnScroll: element.hasAttribute('data-hide-on-scroll') ? true : false, | |||
| tolerance: element.hasAttribute('data-tolerance') ? element.getAttribute('data-tolerance') : 0, | |||
| hideOffset: element.hasAttribute('data-hide-offset') ? element.getAttribute('data-hide-offset') : null, | |||
| shadow: element.hasAttribute('data-sticky-shadow') ? true : false | |||
| }); | |||
| }); | |||
| } | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultconfig() { | |||
| return { | |||
| sticky: false, | |||
| stickyOffset: 0, | |||
| hideOnScroll: false, | |||
| tolerance: 0, | |||
| hideOffset: null, | |||
| shadow: false | |||
| }; | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created plugin instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| // Work out the parent if it hasn't been supplied as an option. | |||
| if (this.parent === null) { | |||
| this.parent = this.config.get('root').parentNode; | |||
| } | |||
| /** | |||
| * The root navbar element. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.root = this.config.get('root'); | |||
| this.root.setAttribute('data-bulma-attached', 'attached'); | |||
| /** | |||
| * The element used for the trigger. | |||
| * @type {HTMLElement} | |||
| */ | |||
| this.triggerElement = this.root.querySelector('.navbar-burger'); | |||
| /** | |||
| * The target element. | |||
| * @type {HTMLELement} | |||
| */ | |||
| this.target = this.root.querySelector('.navbar-menu'); | |||
| /** | |||
| * Should this navbar stick to the top of the page? | |||
| * @type {boolean} | |||
| */ | |||
| this.sticky = typeof window === 'object' && !!this.config.get('sticky'); | |||
| /** | |||
| * The offset in pixels before the navbar will stick to the top of the page | |||
| * @type {number} | |||
| */ | |||
| this.stickyOffset = parseInt(this.config.get('stickyOffset')); | |||
| /** | |||
| * Should the navbar hide when scrolling? Note: this just applies a 'is-hidden-scroll' class. | |||
| * @type {boolean} | |||
| */ | |||
| this.hideOnScroll = !!this.config.get('hideOnScroll'); | |||
| /** | |||
| * The amount of tolerance required before checking the navbar should hide/show | |||
| * @type {number} | |||
| */ | |||
| this.tolerance = parseInt(this.config.get('tolerance')); | |||
| /** | |||
| * Add a shadow when the navbar is sticky? | |||
| * @type {boolean} | |||
| */ | |||
| this.shadow = !!this.config.get('shadow'); | |||
| /** | |||
| * The offset in pixels before the navbar will be hidden, defaults to the height of the navbar | |||
| * @type {number} | |||
| */ | |||
| this.hideOffset = parseInt(this.config.get('hideOffset', Math.max(this.root.scrollHeight, this.stickyOffset))); | |||
| /** | |||
| * The last scroll Y known, this is used to calculate scroll direction | |||
| * @type {number} | |||
| */ | |||
| this.lastScrollY = 0; | |||
| /** | |||
| * An array of any navbar dropdowns | |||
| * @type {NodeList} | |||
| */ | |||
| this.dropdowns = this.root.querySelectorAll('.navbar-item.has-dropdown:not(.is-hoverable)'); | |||
| /** | |||
| * Bind the relevant event handlers to this instance. So that we can remove them if needed | |||
| */ | |||
| this.handleScroll = this.handleScroll.bind(this); | |||
| Bulma(this.root).data('navbar', this); | |||
| this.registerEvents(); | |||
| } | |||
| /** | |||
| * Register all the events this module needs. | |||
| * @return {undefined} | |||
| */ | |||
| registerEvents() { | |||
| if(this.triggerElement) { | |||
| this.triggerElement.addEventListener('click', this.handleTriggerClick.bind(this)); | |||
| } | |||
| if (this.sticky) { | |||
| this.enableSticky(); | |||
| } | |||
| Bulma.each(this.dropdowns, (dropdown) => { | |||
| dropdown.addEventListener('click', this.handleDropdownTrigger); | |||
| }); | |||
| } | |||
| /** | |||
| * Handle the click event on the trigger. | |||
| * @return {undefined} | |||
| */ | |||
| handleTriggerClick() { | |||
| if (this.target.classList.contains('is-active')) { | |||
| this.target.classList.remove('is-active'); | |||
| this.triggerElement.classList.remove('is-active'); | |||
| } else { | |||
| this.target.classList.add('is-active'); | |||
| this.triggerElement.classList.add('is-active'); | |||
| } | |||
| } | |||
| /** | |||
| * Handle the scroll event | |||
| * @return {undefined} | |||
| */ | |||
| handleScroll() { | |||
| this.toggleSticky(window.pageYOffset); | |||
| } | |||
| /** | |||
| * Handle the click handler for any dropdowns found within the navbar | |||
| */ | |||
| handleDropdownTrigger() { | |||
| if (this.classList.contains('is-active')) { | |||
| this.classList.remove('is-active'); | |||
| } else { | |||
| this.classList.add('is-active'); | |||
| } | |||
| } | |||
| /** | |||
| * Enable the sticky feature by attaching the scroll event. | |||
| */ | |||
| enableSticky() { | |||
| window.addEventListener('scroll', this.handleScroll); | |||
| this.root.setAttribute('data-sticky', ''); | |||
| this.sticky = true; | |||
| } | |||
| /** | |||
| * Disable the sticky feature by removing the scroll event. | |||
| */ | |||
| disableSticky() { | |||
| window.removeEventListener('scroll', this.handleScroll); | |||
| this.root.removeAttribute('data-sticky'); | |||
| this.sticky = false; | |||
| } | |||
| /** | |||
| * Enable hide on scroll. Also enable sticky if it's not already. | |||
| */ | |||
| enableHideOnScroll() { | |||
| if (!this.sticky) { | |||
| this.enableSticky(); | |||
| } | |||
| this.root.setAttribute('data-hide-on-scroll', ''); | |||
| this.hideOnScroll = true; | |||
| } | |||
| /** | |||
| * Disable hide on scroll, and show the navbar again if it's hidden. | |||
| */ | |||
| disableHideOnScroll() { | |||
| this.root.removeAttribute('data-hide-on-scroll'); | |||
| this.hideOnScroll = false; | |||
| this.root.classList.remove('is-hidden-scroll'); | |||
| } | |||
| /** | |||
| * Toggle the navbar's sticky state | |||
| * @param {number} scrollY The amount of pixels that has been scrolled | |||
| * @return {undefined} | |||
| */ | |||
| toggleSticky(scrollY) { | |||
| if (scrollY > this.stickyOffset) { | |||
| this.root.classList.add('is-fixed-top'); | |||
| document.body.classList.add('has-navbar-fixed-top'); | |||
| if (this.shadow) { | |||
| this.root.classList.add('has-shadow'); | |||
| } | |||
| } else { | |||
| this.root.classList.remove('is-fixed-top'); | |||
| document.body.classList.remove('has-navbar-fixed-top'); | |||
| if (this.shadow) { | |||
| this.root.classList.remove('has-shadow'); | |||
| } | |||
| } | |||
| if (this.hideOnScroll) { | |||
| let scrollDirection = this.calculateScrollDirection(scrollY, this.lastScrollY); | |||
| let triggeredTolerance = this.difference(scrollY, this.lastScrollY) >= this.tolerance; | |||
| if (scrollDirection === 'down') { | |||
| // only hide the navbar at the top if we reach a certain offset so the hiding is more smooth | |||
| let isBeyondTopOffset = scrollY > this.hideOffset; | |||
| if (triggeredTolerance && isBeyondTopOffset) { | |||
| this.root.classList.add('is-hidden-scroll'); | |||
| } | |||
| } else { | |||
| // if scrolling up to the very top where the navbar would be by default always show it | |||
| let isAtVeryTop = scrollY < this.hideOffset; | |||
| if (triggeredTolerance || isAtVeryTop) { | |||
| this.root.classList.remove('is-hidden-scroll'); | |||
| } | |||
| } | |||
| this.lastScrollY = scrollY; | |||
| } | |||
| } | |||
| difference(a, b) { | |||
| if (a > b) { | |||
| return a - b; | |||
| } else { | |||
| return b - a; | |||
| } | |||
| } | |||
| calculateScrollDirection(currentY, lastY) { | |||
| return currentY >= lastY ? 'down' : 'up'; | |||
| } | |||
| } | |||
| Bulma.registerPlugin('navbar', Navbar); | |||
| export default Bulma; | |||
| @ -0,0 +1,71 @@ | |||
| import Bulma from '../core'; | |||
| import DismissableComponent from '../dismissableComponent'; | |||
| /** | |||
| * @module Notification | |||
| * @since 0.1.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| * @extends DismissableComponent | |||
| */ | |||
| export class Notification extends DismissableComponent { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HTMLElement} element The root element for this instance | |||
| * @return {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.contains('notification')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.notification'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| let bulmaElement = Bulma(element); | |||
| if (bulmaElement.data('notification')) { | |||
| return; | |||
| } | |||
| let closeBtn = element.querySelector('.delete'); | |||
| bulmaElement.notification({ | |||
| body: null, | |||
| closeButton: closeBtn, | |||
| isDismissable: !!closeBtn, | |||
| destroyOnDismiss: true, | |||
| dismissInterval: element.hasAttribute('data-dismiss-interval') ? element.getAttribute('data-dismiss-interval') : null | |||
| }); | |||
| }); | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created instance | |||
| */ | |||
| constructor(config, root) { | |||
| super('notification', config, root); | |||
| // TODO: Move this into the DismissableComponent class. Due to the required | |||
| // changes between different components, we may need a way to trigger this | |||
| // when the component is ready. | |||
| if (this.isDismissable) { | |||
| if (!this.config.has('closeButton')) { | |||
| this.prependCloseButton(); | |||
| } | |||
| this.setupCloseEvent(); | |||
| } | |||
| Bulma(this.root).data('notification', this); | |||
| this.trigger('init'); | |||
| } | |||
| } | |||
| Bulma.registerPlugin('notification', Notification); | |||
| export default Bulma; | |||
| @ -0,0 +1,164 @@ | |||
| import Bulma from '../core'; | |||
| import Plugin from '../plugin'; | |||
| /** | |||
| * @module PanelTabs | |||
| * @since 0.12.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class PanelTabs extends Plugin { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HTMLElement} context The root element for this instance | |||
| * @returns {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.contains('panel')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.panel'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| if(element.querySelector('.panel-tabs') === null) { | |||
| return; | |||
| } | |||
| Bulma(element).panelTabs(); | |||
| }); | |||
| } | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultConfig() { | |||
| return {}; | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| /** | |||
| * The root tab element | |||
| * @param {HTMLElement} | |||
| */ | |||
| this.root = this.config.get('root'); | |||
| this.root.setAttribute('data-bulma-attached', 'attached'); | |||
| /** | |||
| * The tab nav container | |||
| * @param {HTMLElement} | |||
| */ | |||
| this.nav = this.findNav(); | |||
| /** | |||
| * The tab's nav items | |||
| * @param {HTMLElement[]} | |||
| */ | |||
| this.navItems = this.findNavItems(); | |||
| /** | |||
| * The tab's content items | |||
| * @param {HTMLElement[]} | |||
| */ | |||
| this.contentItems = this.findContentItems(); | |||
| this.setupNavEvents(); | |||
| this.on('init', this.showActiveTab.bind(this)); | |||
| Bulma(this.root).data('panelTabs', this); | |||
| this.trigger('init'); | |||
| } | |||
| /** | |||
| * Find the tab navigation container. | |||
| * @returns {HTMLElement} The navigation container | |||
| */ | |||
| findNav() { | |||
| return this.root.querySelector('.panel-tabs'); | |||
| } | |||
| /** | |||
| * Find each individual tab item | |||
| * @returns {NodeListOf<Element>} An array of the found items | |||
| */ | |||
| findNavItems() { | |||
| return this.nav.querySelectorAll('a'); | |||
| } | |||
| /** | |||
| * Find each individual content item | |||
| * @returns {NodeListOf<Element>} An array of the found items | |||
| */ | |||
| findContentItems() { | |||
| return this.root.querySelectorAll('.panel-block[data-category]'); | |||
| } | |||
| /** | |||
| * Setup the events to handle tab changing | |||
| * @returns {void} | |||
| */ | |||
| setupNavEvents() { | |||
| Bulma.each(this.navItems, (navItem) => { | |||
| navItem.addEventListener('click', () => { | |||
| this.setActive(navItem.getAttribute('data-target')); | |||
| }); | |||
| }); | |||
| } | |||
| /** | |||
| * Show the correct category and mark the tab as active. | |||
| * | |||
| * @param {string|null} category The new category to set | |||
| */ | |||
| setActive(category) { | |||
| this.navItems.forEach((item) => { | |||
| if(item.getAttribute('data-target') === category) { | |||
| item.classList.add('is-active'); | |||
| } else { | |||
| item.classList.remove('is-active'); | |||
| } | |||
| }); | |||
| this.contentItems.forEach((item) => { | |||
| if(item.getAttribute('data-category') === category || category === null) { | |||
| item.classList.remove('is-hidden'); | |||
| } else { | |||
| item.classList.add('is-hidden'); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * This is called on init and will setup the panel tabs for the current active tab, if any | |||
| */ | |||
| showActiveTab() { | |||
| let activeNavFound = false; | |||
| Bulma.each(this.navItems, (navItem) => { | |||
| if(navItem.classList.contains('is-active')) { | |||
| this.setActive(navItem.getAttribute('data-target')); | |||
| activeNavFound = true; | |||
| } | |||
| }); | |||
| // If no nav item has is-active then use the first one | |||
| if(!activeNavFound) { | |||
| this.setActive(this.navItems[0].getAttribute('data-target')); | |||
| } | |||
| } | |||
| } | |||
| Bulma.registerPlugin('panelTabs', PanelTabs); | |||
| export default Bulma; | |||
| @ -0,0 +1,167 @@ | |||
| import Bulma from '../core'; | |||
| import Plugin from '../plugin'; | |||
| /** | |||
| * @module Tabs | |||
| * @since 0.4.0 | |||
| * @author Thomas Erbe <vizuaalog@gmail.com> | |||
| */ | |||
| export class Tabs extends Plugin { | |||
| /** | |||
| * Handle parsing the DOMs data attribute API. | |||
| * @param {HTMLElement} element The root element for this instance | |||
| * @returns {undefined} | |||
| */ | |||
| static parseDocument(context) { | |||
| let elements; | |||
| if (typeof context.classList === 'object' && context.classList.has('tabs-wrapper')) { | |||
| elements = [context]; | |||
| } else { | |||
| elements = context.querySelectorAll('.tabs-wrapper'); | |||
| } | |||
| Bulma.each(elements, (element) => { | |||
| Bulma(element).tabs({ | |||
| hover: element.hasAttribute('data-hover') ? true : false | |||
| }); | |||
| }); | |||
| } | |||
| /** | |||
| * Returns an object containing the default config for this plugin. | |||
| * @returns {object} The default config object. | |||
| */ | |||
| static defaultConfig() { | |||
| return { | |||
| hover: false | |||
| }; | |||
| } | |||
| /** | |||
| * Plugin constructor | |||
| * @param {Object} config The config object for this plugin | |||
| * @return {this} The newly created instance | |||
| */ | |||
| constructor(config, root) { | |||
| super(config, root); | |||
| /** | |||
| * The root tab element | |||
| * @param {HTMLElement} | |||
| */ | |||
| this.root = this.config.get('root'); | |||
| this.root.setAttribute('data-bulma-attached', 'attached'); | |||
| /** | |||
| * Whether the tabs should be changed when the nav item is hovered over | |||
| * @param {boolean} | |||
| */ | |||
| this.hover = this.config.get('hover'); | |||
| /** | |||
| * The tab nav container | |||
| * @param {HTMLElement} | |||
| */ | |||
| this.nav = this.findNav(); | |||
| /** | |||
| * The tab's nav items | |||
| * @param {HTMLElement[]} | |||
| */ | |||
| this.navItems = this.findNavItems(); | |||
| /** | |||
| * The tab content container | |||
| * @param {HTMLElement} | |||
| */ | |||
| this.content = this.findContent(); | |||
| /** | |||
| * The tab's content items | |||
| * @param {HTMLElement[]} | |||
| */ | |||
| this.contentItems = this.findContentItems(); | |||
| this.setupNavEvents(); | |||
| Bulma(this.root).data('tabs', this); | |||
| this.trigger('init'); | |||
| } | |||
| /** | |||
| * Find the tab navigation container. | |||
| * @returns {HTMLElement} The navigation container | |||
| */ | |||
| findNav() { | |||
| return this.root.querySelector('.tabs'); | |||
| } | |||
| /** | |||
| * Find each individual tab item | |||
| * @returns {HTMLElement[]} An array of the found items | |||
| */ | |||
| findNavItems() { | |||
| return this.nav.querySelectorAll('li'); | |||
| } | |||
| /** | |||
| * Find the tab content container. | |||
| * @returns {HTMLElement} The content container | |||
| */ | |||
| findContent() { | |||
| return this.root.querySelector('.tabs-content'); | |||
| } | |||
| /** | |||
| * Find each individual content item | |||
| * @returns {HTMLElement[]} An array of the found items | |||
| */ | |||
| findContentItems() { | |||
| // We have to use the root here as the querySelectorAll API doesn't | |||
| // support using '>' as the first character. So we have to have a | |||
| // class to start with. | |||
| return this.root.querySelectorAll('.tabs-content > ul > li'); | |||
| } | |||
| /** | |||
| * Setup the events to handle tab changing | |||
| * @returns {void} | |||
| */ | |||
| setupNavEvents() { | |||
| Bulma.each(this.navItems, (navItem, index) => { | |||
| navItem.addEventListener('click', () => { | |||
| this.setActive(index); | |||
| }); | |||
| if (this.hover) { | |||
| navItem.addEventListener('mouseover', () => { | |||
| this.setActive(index); | |||
| }); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * Set the provided tab's index as the active tab. | |||
| * | |||
| * @param {integer} index The new index to set | |||
| */ | |||
| setActive(index) { | |||
| Bulma.each(this.navItems, (navItem) => { | |||
| navItem.classList.remove('is-active'); | |||
| }); | |||
| Bulma.each(this.contentItems, (contentItem) => { | |||
| contentItem.classList.remove('is-active'); | |||
| }); | |||
| this.navItems[index].classList.add('is-active'); | |||
| this.contentItems[index].classList.add('is-active'); | |||
| } | |||
| } | |||
| Bulma.registerPlugin('tabs', Tabs); | |||
| export default Bulma; | |||
| @ -0,0 +1,22 @@ | |||
| # BulmaJS Supporters | |||
| **This file serves as a directory for the kind, generous BulmaJS users who have supported the BulmaJS project.** | |||
| # Patron | |||
| The supporters below are those who are currently or have been a regular supporter of this project on [Patreon](https://www.patreon.com/vizuaalog). | |||
| ### Legendary supporters | |||
| There are currently no supporters of this tier. | |||
| ### Very generous supporters | |||
| There are currently no supporters of this tier. | |||
| ### Generous supporters | |||
| There are currently no supporters of this tier. | |||
| ### Awesome supporters | |||
| There are currently no supporters of this tier. | |||
| # Paypal | |||
| **The supports below are those who have generously donated via Paypal.** | |||
| Andreas Pantle | |||
| @ -0,0 +1,70 @@ | |||
| QUnit.module('Alert'); | |||
| let alert; | |||
| QUnit.testDone(function() { | |||
| if(!alert) return; | |||
| alert.destroy(); | |||
| }); | |||
| QUnit.test('Correct root classes are added', function(assert) { | |||
| alert = Bulma().alert({ | |||
| title: 'Test alert' | |||
| }); | |||
| assert.ok(document.body.querySelector('.alert'), 'The alert class has been added.'); | |||
| assert.ok(document.body.querySelector('.modal'), 'The modal class has been added.'); | |||
| }); | |||
| QUnit.test('The header is created if showHeader is true', function(assert) { | |||
| alert = Bulma().alert({ | |||
| title: "Hello world" | |||
| }); | |||
| assert.ok(alert.root.querySelector('.modal-card-head'), 'The header is created'); | |||
| assert.ok(alert.root.querySelector('.modal-card-head').innerHTML.indexOf('Hello world') !== -1, 'The header is created'); | |||
| }); | |||
| QUnit.test('The header is not created if showHeader is false', function(assert) { | |||
| alert = Bulma().alert({ | |||
| title: "Hello world", | |||
| showHeader: false | |||
| }); | |||
| assert.notOk(alert.root.querySelector('.modal-card-head'), 'The header is created'); | |||
| }); | |||
| QUnit.test('The body is created with the supplied text', function(assert) { | |||
| alert = Bulma().alert({ | |||
| body: 'Hello world' | |||
| }); | |||
| assert.ok(alert.root.querySelector('.modal-card-body'), 'The body is created'); | |||
| assert.ok(alert.root.querySelector('.modal-card-body').innerHTML.indexOf('Hello world') !== -1, 'The body is created'); | |||
| }); | |||
| QUnit.test('The footer is created', function(assert) { | |||
| alert = Bulma().alert({}); | |||
| assert.ok(alert.root.querySelector('.modal-card-foot'), 'The footer is created'); | |||
| }); | |||
| QUnit.test('Confirm button is created', function(assert) { | |||
| alert = Bulma().alert({ | |||
| type: 'danger', | |||
| confirm: 'Okay' | |||
| }); | |||
| assert.ok(alert.root.querySelector('button.is-danger'), 'The confirm button is created'); | |||
| assert.ok(alert.root.querySelector('button.is-danger').innerHTML.indexOf('Okay') !== -1); | |||
| }); | |||
| QUnit.test('Cancel button is created when the cancel prop is supplied', function(assert) { | |||
| alert = Bulma().alert({ | |||
| type: 'danger', | |||
| cancel: 'Nope' | |||
| }); | |||
| assert.ok(alert.root.querySelectorAll('button').length === 2, 'The confirm button is created'); | |||
| assert.ok(alert.root.innerHTML.indexOf('Nope') !== -1); | |||
| }); | |||
| @ -0,0 +1,69 @@ | |||
| QUnit.module('Core'); | |||
| QUnit.test('A plugin can be registered with core', function(assert) { | |||
| createTestPlugin(); | |||
| assert.ok(Bulma.plugins.hasOwnProperty('testplugin'), 'Plugin reference is added to the plugins object.'); | |||
| assert.ok(Bulma.plugins.testplugin.hasOwnProperty('handler'), 'Plugin reference has a handler property.'); | |||
| assert.ok(Bulma.plugins.testplugin.hasOwnProperty('priority'), 'Plugin reference has a priority property.'); | |||
| assert.ok(Bulma.plugins.testplugin.priority === 1, 'The priority of a plugin is correctly set.'); | |||
| assert.ok(Bulma.prototype.hasOwnProperty('testplugin'), 'Plugin reference added to the Bulma class instance.'); | |||
| }); | |||
| QUnit.test('That the Bulma document parser correctly initialises a plugin', function(assert) { | |||
| createTestPlugin(); | |||
| let elem = document.createElement('div'); | |||
| document.body.appendChild(testCreateElem('div', 'testplugin', 'firsttest')); | |||
| Bulma.parseDocument(document.body); | |||
| assert.ok(Bulma('.firsttest').data('testplugin'), 'A single instance of testplugin has been created.'); | |||
| }); | |||
| QUnit.test('That the Bulma document parser correctly intialises multiple instances of a plugin', function(assert) { | |||
| createTestPlugin(); | |||
| document.body.appendChild(testCreateElem('div', 'testplugin', 'secondtest')); | |||
| document.body.appendChild(testCreateElem('div', 'testplugin', 'thirdtest')); | |||
| Bulma.parseDocument(document.body); | |||
| assert.ok(Bulma('.secondtest').data('testplugin'), 'The first div has an instance of test plugin created.'); | |||
| assert.ok(Bulma('.thirdtest').data('testplugin'), 'The second div has an instance of test plugin created.'); | |||
| }); | |||
| QUnit.test('The createElement method will return the correct element with classes', function(assert) { | |||
| let myElement = Bulma.createElement('div', ['testclass1', 'testclass2']); | |||
| assert.ok(myElement.tagName === 'DIV', 'The method creates the correct type of element.'); | |||
| assert.ok(myElement.classList.contains('testclass1'), 'The first class is added to the element.'); | |||
| assert.ok(myElement.classList.contains('testclass2'), 'The second class is added to the element.'); | |||
| }); | |||
| QUnit.test('The findOrCreateElement method will return an existing element, or create a new one', function(assert) { | |||
| let myElement2 = testCreateElem('div', 'myelement2'); | |||
| document.body.appendChild(myElement2); | |||
| let myElement3 = Bulma.findOrCreateElement('.elementmissing', document.body, 'div', ['myelement3']); | |||
| assert.equal(Bulma.findOrCreateElement('.myelement2'), myElement2, 'The existing element is returned without creating a new one'); | |||
| assert.ok(myElement3 instanceof HTMLElement, 'The new element is created and returned as it does not exist.'); | |||
| }); | |||
| QUnit.test('The stripScripts method will remove any scripts from the string', function(assert) { | |||
| let myString = 'This is a <script>alert("hello world")</script> test'; | |||
| assert.equal(Bulma._stripScripts(myString), 'This is a test', 'The script is removed from the string.'); | |||
| }) | |||
| QUnit.test('Data can be added and retrieved to/from an element', function(assert) { | |||
| let myElement4 = Bulma('.myelement2'); | |||
| myElement4.data('hello', 'world'); | |||
| assert.equal(myElement4.data('hello'), 'world', 'The data has been set and matches when retrieved.'); | |||
| }); | |||
| @ -0,0 +1,19 @@ | |||
| function testCreateElem(type, ...classes) { | |||
| let e = document.createElement(type); | |||
| e.classList.add(...classes); | |||
| return e; | |||
| } | |||
| function createTestPlugin() { | |||
| Bulma.registerPlugin('testplugin', class TestPlugin { | |||
| static create(config) { return 'testplugin'; } | |||
| static parseDocument(context) { | |||
| let elements = context.querySelectorAll('.testplugin'); | |||
| Bulma.each(elements, (element) => { | |||
| Bulma(element).data('testplugin', new TestPlugin({ element: element })); | |||
| }); | |||
| }; | |||
| }, 1); | |||
| } | |||
| @ -0,0 +1,21 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width"> | |||
| <title>BulmaJS Tests</title> | |||
| <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css"> | |||
| </head> | |||
| <body> | |||
| <div id="qunit"></div> | |||
| <div id="qunit-fixture"></div> | |||
| <script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script> | |||
| <script src="helpers.js"></script> | |||
| <script src="core.js"></script> | |||
| <script src="alert.js"></script> | |||
| <script src="../dist/bulma.js"></script> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,25 @@ | |||
| const path = require('path'); | |||
| var entry = require('webpack-glob-entry'); | |||
| module.exports = { | |||
| entry: entry('./src/bulma.js', './src/plugins/*.js'), | |||
| output: { | |||
| filename: '[name].js', | |||
| path: path.resolve(__dirname, 'dist'), | |||
| library: 'Bulma', | |||
| libraryTarget: 'umd', | |||
| libraryExport: 'default', | |||
| umdNamedDefine: true | |||
| }, | |||
| module: { | |||
| rules: [ | |||
| { | |||
| test: /\.js$/, | |||
| exclude: /node_modules/, | |||
| use: { | |||
| loader: 'babel-loader' | |||
| } | |||
| } | |||
| ] | |||
| } | |||
| }; | |||
| @ -1,88 +1,94 @@ | |||
| {% extends 'base.html' %} | |||
| {% block content %} | |||
| <div class="tabs is-toggle is-fullwidth" id="tabs"> | |||
| <!-- | |||
| <button class="tablinks" onclick="openTab(event, 'Songs')">Songs</button> | |||
| <button class="tablinks" onclick="openTab(event, 'Albums')">Albums</button> | |||
| --> | |||
| <ul> | |||
| <li class="is-active" id="tablinks"> | |||
| <a onclick="openTab(event, 'Songs')"> | |||
| <span class="icon is-small"><i class="fa fa-image"></i></span> | |||
| <span>Canciones</span> | |||
| </a> | |||
| </li> | |||
| <li id="tablinks"> | |||
| <a onclick="openTab(event, 'Albums')"> | |||
| <span class="icon is-small"><i class="fa fa-music"></i></span> | |||
| <span>Álbumes</span> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| <div class="tabs-wrapper"> | |||
| <div class="tabs is-toggle is-fullwidth" id="tabs"> | |||
| <!-- | |||
| <button class="tablinks" onclick="openTab(event, 'Songs')">Songs</button> | |||
| <button class="tablinks" onclick="openTab(event, 'Albums')">Albums</button> | |||
| --> | |||
| <ul> | |||
| <li class="is-active" id="tablinks"> | |||
| <a onclick="openTab(event, 'Songs')"> | |||
| <span class="icon is-small"><i class="fa fa-image"></i></span> | |||
| <span>Canciones</span> | |||
| </a> | |||
| </li> | |||
| <li id="tablinks"> | |||
| <a onclick="openTab(event, 'Albums')"> | |||
| <span class="icon is-small"><i class="fa fa-music"></i></span> | |||
| <span>Álbumes</span> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| <div id="Songs" class="tabcontent is-active"> | |||
| <h2>Song List</h2> | |||
| {% if songs %} | |||
| <table id="songTable" class="display"> | |||
| <thead> | |||
| <tr> | |||
| <th>Pista</th> | |||
| <th>Título</th> | |||
| <th>Autor</th> | |||
| <th>Álbum</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for song in songs %} | |||
| <tr> | |||
| <td>{{ song.pista }}</td> | |||
| <td><a href="{{ url_for('paginas.song', song_id=song.id) }}">{{ song.title }}</a></td> | |||
| <td>{{ song.author }}</td> | |||
| <td><a href="{{ url_for('paginas.album', album_id=song.album.id) }}">{{ song.album.name }}</a></td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No songs found.</p> | |||
| {% endif %} | |||
| </div> | |||
| <div class="tabs-content"> | |||
| <ul> | |||
| <li class="is-active"> | |||
| <h2>Song List</h2> | |||
| {% if songs %} | |||
| <table id="songTable" class="display"> | |||
| <thead> | |||
| <tr> | |||
| <th>Pista</th> | |||
| <th>Título</th> | |||
| <th>Autor</th> | |||
| <th>Álbum</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for song in songs %} | |||
| <tr> | |||
| <td>{{ song.pista }}</td> | |||
| <td><a href="{{ url_for('paginas.song', song_id=song.id) }}">{{ song.title }}</a></td> | |||
| <td>{{ song.author }}</td> | |||
| <td><a href="{{ url_for('paginas.album', album_id=song.album.id) }}">{{ song.album.name }}</a></td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No songs found.</p> | |||
| {% endif %} | |||
| </li> | |||
| <div id="Albums" class="tabcontent"> | |||
| <h2>Album List</h2> | |||
| {% if albums %} | |||
| <table id="albumTable" class="display"> | |||
| <thead> | |||
| <tr> | |||
| <th>Cover</th> | |||
| <th>Nombre</th> | |||
| <th>Artista</th> | |||
| <th>Año</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for album in albums %} | |||
| <tr> | |||
| <td> | |||
| {% if album.cover_image %} | |||
| <img src="{{ url_for('paginas.uploaded_file', filename=album.cover_image) }}" alt="{{ album.name }}" style="width:50px;height:50px;"> | |||
| {% else %} | |||
| Sin imágen | |||
| {% endif %} | |||
| </td> | |||
| <td><a href="{{ url_for('paginas.album', album_id=album.id) }}">{{ album.name }}</a></td> | |||
| <td>{{ album.artist }}</td> | |||
| <td>{{ album.year }}</td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No albums found.</p> | |||
| {% endif %} | |||
| <hr> | |||
| <a href="{{ url_for('paginas.add_album') }}" class="button">Añadir nuevo álbum</a> | |||
| <li> | |||
| <h2>Album List</h2> | |||
| {% if albums %} | |||
| <table id="albumTable" class="display"> | |||
| <thead> | |||
| <tr> | |||
| <th>Cover</th> | |||
| <th>Nombre</th> | |||
| <th>Artista</th> | |||
| <th>Año</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for album in albums %} | |||
| <tr> | |||
| <td> | |||
| {% if album.cover_image %} | |||
| <img src="{{ url_for('paginas.uploaded_file', filename=album.cover_image) }}" alt="{{ album.name }}" style="width:50px;height:50px;"> | |||
| {% else %} | |||
| Sin imágen | |||
| {% endif %} | |||
| </td> | |||
| <td><a href="{{ url_for('paginas.album', album_id=album.id) }}">{{ album.name }}</a></td> | |||
| <td>{{ album.artist }}</td> | |||
| <td>{{ album.year }}</td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No albums found.</p> | |||
| {% endif %} | |||
| <hr> | |||
| <a href="{{ url_for('paginas.add_album') }}" class="button">Añadir nuevo álbum</a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| {% endblock %} | |||