Phantom Js Save File Offered for Download Pdf Rendering with Phantomjs

Total Page:16

File Type:pdf, Size:1020Kb

Phantom Js Save File Offered for Download Pdf Rendering with Phantomjs phantom js save file offered for download Pdf Rendering With Phantomjs. When it comes to generating printable documents in your Rails App a lot of people use prawn to create pdfs. Although prawn is a very powerful gem for this kind of task, things can easily get complicated if you have complex styling . At adeven we use the powerful d3js library to create daily reports for our adjust.io customers. Unfortunately, adding javascript-based content to your pdf is impossible with prawn, so we turned to PhantomJS. Generate pdf from html with PhantomJS. PhantomJS is a headless WebKit with JavaScript API. It’s well known for headless website testing in CI environments - check out Poltergeist to learn more about Testing JavaScript with PhantomJS. However, PhantomJS can also be used for screen-capturing as well as generating pdf documents. Shrimp. Our shrimp gem is a simple wrapper around PhantomJS’s pdf-rendering capabilities. You can download and install PhantomJS from http://phantomjs.org/download.html or simply do a. if you are on MacOS using homebrew. To install shrimp just type. Et voilá! A rendered pdf of your website. Shrimp comes with plenty of options that you can pass to the Phantom Object. However, you can also configure shrimp to your needs with a config file: Most of the options are self explanatory. Note that if you have very complex pages with a lot of javascript that needs to be executed after document ready, you might want to increase the rendering_time. For example with our d3js graphs we need 3 seconds to get good results. Keep in mind, that the rendering_timeout should be higher than the rendering_time. The Phantom Class come with three different rendering options: Shit’s being weird. If some error occurs you will still get a result - an empty file. This is necessary to let some asynchronous rendering like Shrimp::Middleware know about it. However you still can check the error response. To make sure the resulting pdf has the expected content, phantom does not follow redirects or render weird 500 status pages. So everything other than a 200 response results in an empty output file. If you prefer bang methods each of the rendering options comes with a bang! Shrimp::Middleware. The shrimp gem comes with a rack-aware Middleware that allows users to get a pdf view of any page on your site by appending .pdf to the URL. Non-Rails Rack apps. Rails apps. With Shrimp options. With conditions to limit routes that can be generated in pdf. Polling. To avoid deadlocks, Shrimp::Middleware renders the pdf in a separate process retuning a 503 Retry-After response Header. you can setup the polling interval and the polling offset in seconds. Caching. To avoid rendering the page on each request you can setup some the cache ttl in seconds. Cookies. If you use Rack::Session::Cookie in your RackApp the user cookie is passed to PhantomJS. Thus you don’t need to worry about Login Credentials or other session based content. However, as we also send pdf reports to our customers we want to render resources without being logged in. Since we use devise for user handling in our Rails App, things get easy with our own devise SignInInterceptor: With this setup we can add a to_pdf method to our resource. Fancy Ajax. The middleware return three different status codes based on the rendering status. To include some fancy Ajax stuff with jquery you can do. CSS Styling. The good thing about PhantomJS is that you only need to take care of webkit’s css implementation. To implement manual page breaks you can do: You don’t always have to fight the fat prawn when a lightweight shrimp can do. Phantom js save file offered for download. ‘read(string path, string/Object parameters)’ (string) The parameter object can contain : mode: Open Mode. A string made of ‘r’, ‘w’, ‘a/+’, ‘b’ characters. charset : An IANA, case insensitive, charset name. Opens the path and returns the entire contents as a string. When errors occur during a call, it will throw a ‘Unable to open file PATH’ and hang execution. Note that the behavior of this method is closely related to fs.open. Examples. ≪ File System Module. P separator P workingDirectory M absolute M changeWorkingDirectory M copyTree M copy M exists M isAbsolute M isDirectory M isExecutable M isFile M isLink M isReadable M isWritable M lastModified M list M makeDirectory M makeTree M move M open M readLink M read M removeDirectory M removeTree M remove M size M touch M write. © Copyright 2010-2020 Ariya Hidayat — Homepage design by Maurice Svay — Documentation design by Jamie Mason. uploadFile. Uploads the specified file ( filename ) to the form element associated with the selector . This function is used to automate the upload of a file, which is usually handled with a file dialog in a traditional browser. Since there is no dialog in this headless mode, such an upload mechanism is handled via this special function instead. Examples. ≪ Web Page Module. P canGoBack P canGoForward P clipRect P content P cookies P customHeaders P event P focusedFrameName P frameContent P frameName P framePlainText P frameTitle P frameUrl P framesCount P framesName P libraryPath P navigationLocked P offlineStoragePath P offlineStorageQuota P ownsPages P pagesWindowName P pages P paperSize P plainText P scrollPosition P settings P title P url P viewportSize P windowName P zoomFactor M addCookie M childFramesCount M childFramesName M clearCookies M close M currentFrameName M deleteCookie M evaluateAsync M evaluateJavaScript M evaluate M getPage M goBack M goForward M go M includeJs M injectJs M openUrl M open M release M reload M renderBase64 M renderBuffer M render M sendEvent M setContent M stop M switchToChildFrame M switchToFocusedFrame M switchToFrame M switchToMainFrame M switchToParentFrame M uploadFile H Callback Triggers H onAlert H onCallback H onClosing H onConfirm H onConsoleMessage H onError H onFilePicker H onInitialized H onLoadFinished H onLoadStarted H onNavigationRequested H onPageCreated H onPrompt H onResourceError H onResourceReceived H onResourceRequested H onResourceTimeout H onUrlChanged. © Copyright 2010-2020 Ariya Hidayat — Homepage design by Maurice Svay — Documentation design by Jamie Mason. Phantom js save file offered for download. Completing the CAPTCHA proves you are a human and gives you temporary access to the web property. What can I do to prevent this in the future? If you are on a personal connection, like at home, you can run an anti-virus scan on your device to make sure it is not infected with malware. If you are at an office or shared network, you can ask the network administrator to run a scan across the network looking for misconfigured or infected devices. Another way to prevent getting this page in the future is to use Privacy Pass. You may need to download version 2.0 now from the Chrome Web Store. Cloudflare Ray ID: 67adb0275e2715e0 • Your IP : 188.246.226.140 • Performance & security by Cloudflare. PhantomJS - Scriptable Headless Browser. Important : PhantomJS development is suspended until further notice (more details). PhantomJS is a headless web browser scriptable with JavaScript. It runs on Windows, macOS, Linux, and FreeBSD. Using QtWebKit as the back-end, it offers fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG. The following simple script for PhantomJS loads Google homepage, waits a bit, and then captures it to an image. PhantomJS is an optimal solution for: Ready to play with PhantomJS? Install and follow the Quick Start guide. Want to learn more? Read the FAQ, explore more examples, and study the complete API documentation..
Recommended publications
  • Netscape 6.2.3 Software for Solaris Operating Environment
    What’s New in Netscape 6.2 Netscape 6.2 builds on the successful release of Netscape 6.1 and allows you to do more online with power, efficiency and safety. New is this release are: Support for the latest operating systems ¨ BETTER INTEGRATION WITH WINDOWS XP q Netscape 6.2 is now only one click away within the Windows XP Start menu if you choose Netscape as your default browser and mail applications. Also, you can view the number of incoming email messages you have from your Windows XP login screen. ¨ FULL SUPPORT FOR MACINTOSH OS X Other enhancements Netscape 6.2 offers a more seamless experience between Netscape Mail and other applications on the Windows platform. For example, you can now easily send documents from within Microsoft Word, Excel or Power Point without leaving that application. Simply choose File, “Send To” to invoke the Netscape Mail client to send the document. What follows is a more comprehensive list of the enhancements delivered in Netscape 6.1 CONFIDENTIAL UNTIL AUGUST 8, 2001 Netscape 6.1 Highlights PR Contact: Catherine Corre – (650) 937-4046 CONFIDENTIAL UNTIL AUGUST 8, 2001 Netscape Communications Corporation ("Netscape") and its licensors retain all ownership rights to this document (the "Document"). Use of the Document is governed by applicable copyright law. Netscape may revise this Document from time to time without notice. THIS DOCUMENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN NO EVENT SHALL NETSCAPE BE LIABLE FOR INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY KIND ARISING FROM ANY ERROR IN THIS DOCUMENT, INCLUDING WITHOUT LIMITATION ANY LOSS OR INTERRUPTION OF BUSINESS, PROFITS, USE OR DATA.
    [Show full text]
  • Automated Testing Clinic Follow-Up: Capybara-Webkit Vs. Poltergeist/Phantomjs | Engineering in Focus
    Automated Testing Clinic follow-up: capybara-webkit vs. polter... https://behindthefandoor.wordpress.com/2014/03/02/automated-... Engineering in Focus the Fandor engineering blog Automated Testing Clinic follow-up: capybara-webkit vs. poltergeist/PhantomJS with 2 comments In my presentation at the February Automated Testing SF meetup I (Dave Schweisguth) noted some problems with Fandor’s testing setup and that we were working to fix them. Here’s an update on our progress. The root cause of several of our problems was that some of the almost 100 @javascript scenarios in our Cucumber test suite weren’t running reliably. They failed occasionally regardless of environment, they failed more on slower CPUs (e.g. MacBook Pros only a couple of years old), when they failed they sometimes hung forever, and when we killed them they left behind webkit-server processes (we were using the capybara-webkit driver) which, if not cleaned up, would poison subsequent runs. Although we’ve gotten pretty good at fixing flaky Cucumber scenarios, we’d been stumped on this little handful. We gave up, tagged them @non_ci and excluded them from our build. But they were important scenarios, so we had to run them manually before deploying. (We weren’t going to just not run them: some of those scenarios tested our subscription process, and we would be fools to deploy a build that for all we knew wouldn’t allow new users to subscribe to Fandor!) That made our release process slower and more error-prone. It occurred to me that I could patch the patch and change our deployment process to require that the @non_ci scenarios had been run (by adding a git tag when those scenarios were run and checking for it when deploying), but before I could put that in to play a new problem appeared.
    [Show full text]
  • Casperjs Documentation Release 1.1.0-DEV Nicolas Perriault
    CasperJs Documentation Release 1.1.0-DEV Nicolas Perriault February 04, 2016 Contents 1 Installation 3 1.1 Prerequisites...............................................3 1.2 Installing from Homebrew (OSX)....................................3 1.3 Installing from npm...........................................4 1.4 Installing from git............................................4 1.5 Installing from an archive........................................4 1.6 CasperJS on Windows..........................................5 1.7 Known Bugs & Limitations.......................................5 2 Quickstart 7 2.1 A minimal scraping script........................................7 2.2 Now let’s scrape Google!........................................8 2.3 CoffeeScript version...........................................9 2.4 A minimal testing script......................................... 10 3 Using the command line 11 3.1 casperjs native options.......................................... 12 3.2 Raw parameter values.......................................... 13 4 Selectors 15 4.1 CSS3................................................... 15 4.2 XPath................................................... 16 5 Testing 17 5.1 Unit testing................................................ 17 5.2 Browser tests............................................... 18 5.3 Setting Casper options in the test environment............................. 19 5.4 Advanced techniques........................................... 20 5.5 Test command args and options....................................
    [Show full text]
  • Webassembly a New World of Native Exploits on the Web Agenda
    WebAssembly A New World Of Native Exploits On The Web Agenda • Introduction • The WebAssembly Platform • Emscripten • Possible Exploit Scenarios • Conclusion Wasm: What is it good for? ● Archive.org web emulators ● Image/processing ● Video Games ● 3D Modeling ● Cryptography Libraries ● Desktop Application Ports Wasm: Crazy Incoming ● Browsix, jslinux ● Runtime.js (Node), Nebulet ● Cervus ● eWASM Java Applet Joke Slide ● Sandboxed ● Virtual Machine, runs its own instruction set ● Runs in your browser ● Write once, run anywhere ● In the future, will be embedded in other targets What Is WebAssembly? ● A relatively small set of low-level instructions ○ Instructions are executed by browsers ● Native code can be compiled into WebAssembly ○ Allows web developers to take their native C/C++ code to the browser ■ Or Rust, or Go, or anything else that can compile to Wasm ○ Improved Performance Over JavaScript ● Already widely supported in the latest versions of all major browsers ○ Not limited to running in browsers, Wasm could be anywhere Wasm: A Stack Machine Text Format Example Linear Memory Model Subtitle Function Pointers Wasm in the Browser ● Wasm doesn’t have access to memory, DOM, etc. ● Wasm functions can be exported to be callable from JS ● JS functions can be imported into Wasm ● Wasm’s linear memory is a JS resizable ArrayBuffer ● Memory can be shared across instances of Wasm ● Tables are accessible via JS, or can be shared to other instances of Wasm Demo: Wasm in a nutshell Emscripten ● Emscripten is an SDK that compiles C/C++ into .wasm binaries ● LLVM/Clang derivative ● Includes built-in C libraries, etc. ● Also produces JS and HTML code to allow easy integration into a site.
    [Show full text]
  • Automated Testing of Your Corporate Website from Multiple Countries with Selenium Contents
    presents Automated Testing of Your Corporate Website from Multiple Countries with Selenium Contents 1. Summary 2. Introduction 3. The Challenges 4. Components of a Solution 5. Steps 6. Working Demo 7. Conclusion 8. Questions & Answers Summary Because of the complexities involved in testing large corporate websites and ecommerce stores from multiple countries, test automation is a must for every web and ecommerce team. Selenium is the most popular, straightforward, and reliable test automation framework with the largest developer community on the market. This white paper details how Selenium can be integrated with a worldwide proxy network to verify website availability, performance, and correctness on a continuous basis. Introduction Modern enterprise web development teams face a number of challenges when they must support access to their website from multiple countries. These challenges include verifying availability, verifying performance, and verifying content correctness on a daily basis. Website content is presented in different languages, website visitors use different browsers and operating systems, and ecommerce carts must comprehend different products and currencies. Because of these complexities involved, instituting automated tests via a test automation framework is the only feasible method of verifying all of these aspects in a repeatable and regular fashion. Why automate tests? Every company tests its products before releasing them to their customers. This process usually involves hiring quality assurance engineers and assigning them to test the product manually before any release. Manual testing is a long process that requires time, attention, and resources in order to validate the products’ quality. The more complex the product is, the more important, complex, and time- consuming the quality assurance process is, and therefore the higher the demand for significant resources.
    [Show full text]
  • Casperjs Documentation Release 1.1.0-DEV
    CasperJs Documentation Release 1.1.0-DEV Nicolas Perriault Sep 13, 2018 Contents 1 Installation 3 1.1 Prerequisites...............................................3 1.2 Installing from Homebrew (OSX)....................................4 1.3 Installing from npm...........................................4 1.4 Installing from git............................................4 1.5 Installing from an archive........................................5 1.6 CasperJS on Windows..........................................5 1.7 Known Bugs & Limitations.......................................6 2 Quickstart 7 2.1 A minimal scraping script........................................7 2.2 Now let’s scrape Google!........................................8 2.3 CoffeeScript version...........................................9 2.4 A minimal testing script......................................... 10 3 Using the command line 11 3.1 casperjs native options.......................................... 12 3.2 Raw parameter values.......................................... 13 4 Selectors 15 4.1 CSS3................................................... 15 4.2 XPath................................................... 16 5 Testing 17 5.1 Unit testing................................................ 17 5.2 Browser tests............................................... 18 5.3 Setting Casper options in the test environment............................. 19 5.4 Advanced techniques........................................... 20 5.5 Test command args and options....................................
    [Show full text]
  • Comparing Javascript Engines
    Comparing Javascript Engines Xiang Pan, Shaker Islam, Connor Schnaith Background: Drive-by Downloads 1. Visiting a malicious website 2. Executing malicious javascript 3. Spraying the heap 4. Exploiting a certain vulnerability 5. Downloading malware 6. Executing malware Background: Drive-by Downloads 1. Visiting a malicious website 2. Executing malicious javascript 3. Spraying the heap 4. Exploiting a certain vulnerability 5. Downloading malware 6. Executing malware Background: Drive-by Downloads Background: Drive-by Downloads Setup: Making the prototype null while in the prototype creates a pointer to something random in the heap. Background: Drive-by Downloads Environment: gc( ) is a function call specific to Firefox, so the attacker would want to spray the heap with an exploit specific to firefox. Background: Drive-by Downloads Obfuscation: If the browser executing the javascript it firefox,the code will proceed to the return statement. Any other browser will exit with an error due to an unrecognized call to gc( ). Background: Drive-by Downloads Download: The return will be to a random location in the heap and due to heap-spraying it will cause shell code to be executed. Background: Goal of Our Project ● The goal is to decode obfuscated scripts by triggering javascript events ● The problem is when triggering events, some errors, resulting from disparity of different engines or some other reasons, may occur and terminate the progress ● We need to find ways to eliminate the errors and Ex 1therefore generate more de-obfuscated scripts <script> function f(){ //some codes gc(); var x=unescape(‘%u4149%u1982%u90 […]’)); eval(x); } </script> Ex 2 <script type="text/javascript" src="/includes/jquery/jquery.js"></script> Project Overview - Part One ● Modify WebKit engine so that it can generate error informations.
    [Show full text]
  • HTML Tips and Tricks
    Here are some common HTML/JavaScript codes to use in your survey building. To use these codes click on the purple drop down to the left of a question. Then click on “add java script”. Replace the part that says” /*Place Your Javascript Here*/” with the codes below. Some codes are to be used in the source view or answer choices. Again these codes should be pasted into your question when you have accessed the “code view” in the upper right of the questions text box or if you’re in the rich text editor click on the “source” button in the upper right corner of the rich text editor. Below you will find 18 codes. The blue text specifies where the code should be placed (java script editor or source view). To customize the codes you will need to change the red text in the codes below. 1. Append text to the right of text entry choices (ex. TE Form) This allows you to ask questions such as "Approx how many hours a day do you watch TV? [TEXT ENTRY SHORT] hrs" (Java script editor) var questionId = this.questionId; var choiceInputs = $$('#'+this.questionId + ' .ChoiceStructure input'); for (var i=0; i < choiceInputs.length; i++) {var choiceInput = choiceInputs[i]; try {if (choiceInput && choiceInput.parentNode) {choiceInput.parentNode.appendChild(QBuilder('span',{},'hrs')); } } catch(e) { } } 2. Puts a calendar into the question (This code only works when placed in the source view. Or use the question saved in the Qualtrics Library. ) <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.7.0/build/calendar/assets/skins/sam/calendar.css">
    [Show full text]
  • Features Guide [email protected] Table of Contents
    Features Guide [email protected] Table of Contents About Us .................................................................................. 3 Make Firefox Yours ............................................................... 4 Privacy and Security ...........................................................10 The Web is the Platform ...................................................11 Developer Tools ..................................................................13 2 About Us About Mozilla Mozilla is a global community with a mission to put the power of the Web in people’s hands. As a nonprofit organization, Mozilla has been a pioneer and advocate for the Web for more than 15 years and is focused on creating open standards that enable innovation and advance the Web as a platform for all. We are committed to delivering choice and control in products that people love and can take across multiple platforms and devices. For more information, visit www.mozilla.org. About Firefox Firefox is the trusted Web browser of choice for half a billion people around the world. At Mozilla, we design Firefox for how you use the Web. We make Firefox completely customizable so you can be in control of creating your best Web experience. Firefox has a streamlined and extremely intuitive design to let you focus on any content, app or website - a perfect balance of simplicity and power. Firefox makes it easy to use the Web the way you want and offers leading privacy and security features to help keep you safe and protect your privacy online. Mozilla continues to move the Web forward by pioneering new open source technologies such as asm.js, Emscripten and WebAPIs. Firefox also has a range of amazing built-in developer tools to provide a friction-free environment for building Web apps and Web content.
    [Show full text]
  • A Plug-In and an XPCOM Component for Controlling Firefox Browser Requests1
    A Plug-in and an XPCOM Component for Controlling Firefox Browser 1 Requests Mozilla’s Firefox offers useful programming APIs to enhance or modify the behavior of browser [2]. The APIs are part of XPCOM (Cross Platform Component Object Model) which provides a set of classes and related methods to perform useful and primitive operations such as file creation and intercepting requests or responses. Most of the XPCOM components are available either through the HTML rendering engine (e.g., Gecko) or JavaScript engine (e.g., SeaMonkey). However, one of the most interesting aspects of Mozilla’s Firefox runtime environment is that it offers us to build customized plug-ins and XPCOM components [1, 4]. It also allows one to implement plug-ins and components in a number of programming languages such as JavaScript. Currently, there exist many plug-ins and XPCOM components that allow programmers to access web pages and browser requests. However, they cannot control browser requests based on HTML form-based requests. This project aims to fill this gap. The project has two parts. First, a plug-in for Firefox browser needs to be developed so that a user can control which requests should be allowed or blocked. To obtain the desired functionalities, one requires implementing a number of JavaScript methods to validate requests (or URLs). The plug-in, when enabled, should perform the following three major functionalities: (i) initialization, (ii) checking requests and HTML forms, (iii) stopping a request and conditionally allowing a request to proceed. The above functionalities will be tested using a set of test cases discussed in [3].
    [Show full text]
  • Webkit DOM Programming Topics
    WebKit DOM Programming Topics Apple Applications > Safari 2008-10-15 MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. AS A RESULT, THIS DOCUMENT IS Apple Inc. PROVIDED “AS IS,” AND YOU, THE READER, ARE © 2004, 2008 Apple Inc. ASSUMING THE ENTIRE RISK AS TO ITS QUALITY AND ACCURACY. All rights reserved. IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR No part of this publication may be reproduced, CONSEQUENTIAL DAMAGES RESULTING FROM ANY stored in a retrieval system, or transmitted, in DEFECT OR INACCURACY IN THIS DOCUMENT, even if advised of the possibility of such damages. any form or by any means, mechanical, THE WARRANTY AND REMEDIES SET FORTH ABOVE electronic, photocopying, recording, or ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL otherwise, without prior written permission of OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer, agent, or employee is authorized to make Apple Inc., with the following exceptions: Any any modification, extension, or addition to this person is hereby authorized to store warranty. documentation on a single computer for Some states do not allow the exclusion or limitation personal use only and to print copies of of implied warranties or liability for incidental or consequential damages, so the above limitation or documentation for personal use provided that exclusion may not apply to you. This warranty gives the documentation contains Apple’s copyright you specific legal rights, and you may also have notice. other rights which vary from state to state. The Apple logo is a trademark of Apple Inc. Use of the “keyboard” Apple logo (Option-Shift-K) for commercial purposes without the prior written consent of Apple may constitute trademark infringement and unfair competition in violation of federal and state laws.
    [Show full text]
  • Extending Adobe Captivate with Javascript
    EXTENDING ADOBE CAPTIVATE WITH JAVASCRIPT ADVANCED TECHNIQUES FROM A WEB DEVELOPER’S PERSPECTIVE HTTPS://GITHUB.COM/SDWARWICK/CAPTIVATE-DEMOS STEVEN WARWICK, ELEARNINGOCEAN.COM [email protected] COPYRIGHT (C)2017 ELEARNING OCEAN LLC 1 AUDIENCE • Learning interaction designers • Project managers / Course strategy developers • Web Developers • eLearning methodology strategists • Content Authors COPYRIGHT (C)2017 ELEARNING OCEAN LLC 2 CONTEXT • Captivate • HTML projects • “Responsive” design • Windows 10 development environment • JavaScript ECMA 2015 • Chrome browser • Notepad++ text editor COPYRIGHT (C)2017 ELEARNING OCEAN LLC 3 PLAN • Captivate as a web development platform • Efficient development of JavaScript/Captivate scripts • Example Scripts • Fully custom quiz interactions • Full-screen mode • D&D • Adobe documented vs. undocumented functions • Bridging between JavaScript and Captivate • Overview of other possibilities with JavaScript • Questions COPYRIGHT (C)2017 ELEARNING OCEAN LLC 4 CAPTIVATE FROM THE WEB DEVELOPERS PERSPECTIVE • WYSIWYG website builders: • “Closed” builders generate sites that cannot easily be modified after being generated • Easy to get started building, limited access to potential of modern design • Weebly, Wix, Squarespace • “Open” builders support direct modification of generated sites & continued editing • Deeper understanding of web technologies needed • Pinegrow, Bootstrap Studio, Bootply • Captivate – 90% closed / 10% open • Custom features valuable for eLearning • Reasonable strategy given
    [Show full text]