Bootstrap Native不适用于Turbo Links

时间:2017-10-20 21:01:35

标签: javascript ruby-on-rails turbolinks turbolinks-5 bootstrap-native

我正在尝试让Bootstrap Native与Rails 5应用中的Turbolinks 5一起使用。当我第一次加载页面时,Bootstrap下拉菜单工作正常,但在导航到另一个页面后,Bootstrap下拉不再有效。就好像Bootstrap的事件监听器断开连接一样。

关于Bootstrap的jQuery实现,我已经看到了几个问题,但是,我有兴趣使用Bootstrap Native并从我的JS堆栈中消除jQuery。

以下是一些细节:

的application.js

    import UIKit

class MainVC: UIViewController {

    let setupMainVC = SetupMainVC()

    var rightViewXConstant : NSLayoutConstraint!
    var leftViewXConstant : NSLayoutConstraint!
    var arrowLeftXConstant : NSLayoutConstraint!
    var centerViewXConstant : NSLayoutConstraint!

    var rightViewOpen = false
    var leftViewOpen = false

    var windowSize = CGFloat()

    override func viewDidLoad() {
        super.viewDidLoad()

        let window = UIWindow.init(frame: UIScreen.main.bounds)
        window.makeKeyAndVisible()

        view.backgroundColor = UIColor.clear

        setupMainVC.mainVC = self
        setupMainVC.setupViews()

        windowSize = window.frame.size.width

        setupLeftViewController()
        setupCenterViewController()


        navigationItem.title = "Center View"

        self.setupMainVC.arrowButtonLeft.transform = CGAffineTransform(rotationAngle: 0)
        arrowLeftXConstant.constant = 20

        print(windowSize)

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Left", style: .plain, target: self, action: #selector(leftButAction))
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Right", style: .plain, target: self, action: #selector(rightButAction))
        setupMainVC.arrowButtonLeft.addTarget(self, action: #selector(leftButAction), for: .touchUpInside)

        NotificationCenter.default.addObserver(self, selector: #selector(leftButAction), name: NSNotification.Name("leftButtonAction"), object: nil)
    }

    func leftButAction() {
        print("Left Action")

        if leftViewOpen {
            leftViewOpen = false
            centerViewXConstant.constant = 0
            leftViewXConstant.constant = 0
            self.setupMainVC.arrowButtonLeft.transform = CGAffineTransform(rotationAngle: 0)
            arrowLeftXConstant.constant = 20
            navigationItem.title = "Center View"
            print(false)
        } else {
            leftViewOpen = true
            centerViewXConstant.constant = windowSize
            leftViewXConstant.constant = windowSize
            self.setupMainVC.arrowButtonLeft.transform = CGAffineTransform(rotationAngle: -179)
            arrowLeftXConstant.constant = -20
            navigationItem.title = "Left View"
            print(true)
        }

        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded()
        }

    }

    func rightButAction() {
        print("Right Action")

        if rightViewOpen {
            rightViewOpen = false
            print(false)
        } else {
            rightViewOpen = true
            print(true)
        }

        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded()
        }
    }

    func center() {
        let center = CenterVC()
        addChildViewController(center)
        center.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(center.view)

        centerViewXConstant = center.view.centerXAnchor.constraint(equalTo: center.view.centerXAnchor, constant: 0)
        centerViewXConstant.isActive = true
        center.view.centerYAnchor.constraint(equalTo: center.view.centerYAnchor).isActive = true
        center.view.heightAnchor.constraint(equalTo: center.view.heightAnchor).isActive = true
        center.view.widthAnchor.constraint(equalTo: center.view.widthAnchor).isActive = true
        center.view.backgroundColor = .blue

        center.didMove(toParentViewController: self)
    }

    //MARK: Center View
    func setupCenterViewController() {
        let controller = CenterVC()

        addChildViewController(controller)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(controller.view)
        controller.view.addSubview(setupMainVC.roundViewOnLeftView)
        controller.view.addSubview(setupMainVC.arrowButtonLeft)

        centerViewXConstant = controller.view.leftAnchor.constraint(equalTo: setupMainVC.leftView.rightAnchor, constant: 0)
        centerViewXConstant.isActive = true

        controller.view.centerYAnchor.constraint(equalTo: setupMainVC.leftView.centerYAnchor).isActive = true
        controller.view.heightAnchor.constraint(equalTo: setupMainVC.leftView.heightAnchor).isActive = true
        controller.view.widthAnchor.constraint(equalTo: setupMainVC.leftView.widthAnchor).isActive = true

        setupMainVC.roundViewOnLeftView.centerXAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
        setupMainVC.roundViewOnLeftView.centerYAnchor.constraint(equalTo: controller.view.centerYAnchor).isActive = true
        setupMainVC.roundViewOnLeftView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        setupMainVC.roundViewOnLeftView.widthAnchor.constraint(equalToConstant: 100).isActive = true

        arrowLeftXConstant = setupMainVC.arrowButtonLeft.centerXAnchor.constraint(equalTo: setupMainVC.roundViewOnLeftView.centerXAnchor, constant: 20)
        arrowLeftXConstant.isActive = true

        setupMainVC.arrowButtonLeft.centerYAnchor.constraint(equalTo: setupMainVC.roundViewOnLeftView.centerYAnchor).isActive = true
        setupMainVC.arrowButtonLeft.heightAnchor.constraint(equalToConstant: 100).isActive = true
        setupMainVC.arrowButtonLeft.widthAnchor.constraint(equalToConstant: 100).isActive = true

        controller.didMove(toParentViewController: self)
    }

    //MARK: Left View
    func setupLeftViewController() {
        let controller = LeftVC()
        //let navBar: UINavigationController = UINavigationController(rootViewController: controller)
        addChildViewController(controller)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(controller.view)

        leftViewXConstant = controller.view.rightAnchor.constraint(equalTo: setupMainVC.centerView.leftAnchor, constant: 0)
        leftViewXConstant.isActive = true

        controller.view.centerYAnchor.constraint(equalTo: setupMainVC.centerView.centerYAnchor).isActive = true
        controller.view.heightAnchor.constraint(equalTo: setupMainVC.centerView.heightAnchor).isActive = true
        controller.view.widthAnchor.constraint(equalTo: setupMainVC.centerView.widthAnchor).isActive = true

        controller.didMove(toParentViewController: self)
    }
}

应用程序布局

# app/assets/javascript/application.js
//= require turbolinks
//= require rails-ujs
//= require polyfill
//= require bootstrap-native

自举天然 这是Bootstrap Native package

从我的Javascript标记中删除# views/layout/application.html.erb <!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> </head> <body> <%= render 'layouts/header' %> <div class="container"> <%= yield %> </div> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', 'data-turbolinks-eval': 'false' %> </body> </html> ,从而在每个Turbolink导航上重新评估应用程序的所有Javascript,确实解决了Bootstrap Native问题,但它会导致rails-ujs抛出异常。< / p>

关于如何解决这个问题的任何想法?

4 个答案:

答案 0 :(得分:2)

BSN开发人员在这里,想为什么不在BSN库外添加这个自定义代码,以便于维护?

var container = document.getElementById('myContainer');
document.addEventListener('turbolinks:load', function(){
  container = container || document.getElementById('myContainer');
  Array.prototype.forEach.call(container.querySelectorAll('[data-spy="affix"]'), function(element){ new Affix(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-dismiss="alert"]'), function(element){ new Alert(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="buttons"]'), function(element){ new Button(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-ride="carousel"]'), function(element){ new Carousel(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="collapse"]'), function(element){ new Collapse(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="dropdown"]'), function(element){ new Dropdown(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="modal"]'), function(element){ new Modal(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="popover"]'), function(element){ new Popover(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-spy="scroll"]'), function(element){ new ScrollSpy(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="tab"]'), function(element){ new Tab(element) });
  Array.prototype.forEach.call(container.querySelectorAll('[data-toggle="tooltip"]'), function(element){ new Tooltip(element) });
},false);

您应该只查看特定的容器turbolinks更新,因为您不更新任何其他内容,并且您需要尽可能快。

我还更新了wiki page以包含一个通用示例。

<强>更新 从BSN版本2.0.20开始,您可以使用网站<head>中的库,而无需任何其他脚本,并且您可以更轻松地执行此操作{/ 1}}:

turbolinks

如果您在控制台中输入var container = document.getElementById('myContainer'); document.addEventListener('turbolinks:load', function(){ container = container || document.getElementById('myContainer'); BSN.initCallback(container); }, false); 按Enter键,则会收到以下对象:

BSN

请记住,您不必使用BSN = { version: '2.0.20', initCallback: function(lookup){}, supports: [ /* an array with supported components */ ] } 作为您的turbolinks目标的ID属性,您可以使用任何内容使该元素唯一,我建议选择myContainer。< / p>

下载最新的主人并尝试一下。

答案 1 :(得分:1)

使用Turbolinks时,您很可能希望将应用JavaScript文件放在<head>中:

# views/layout/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

虽然这似乎违背了传统的最佳做法,但Turbolinks的性能优势在于脚本仅在初始页面加载时加载。

如果引导程序本机库在DOMContentLoaded上初始化其插件/函数,您可能还需要在turbolinks:load上手动调用这些函数,然后将其拆除(通常在turbolinks:before-cache上)如果有必要的话。

答案 2 :(得分:1)

主要修订

在Bootstrap Native开发团队成员接受回答后,我修改了我在应用程序中实现的解决方案。我的解决方案与公认的解决方案略有不同,因为我的监听器将在整个DOM中搜索Bootstrap组件。相反,接受的解决方案只会在具有id="myContainer的HTML标记内进行搜索。接受的解决方案将执行得更快,因为它只搜索DOM的子集。但是,它要求开发人员将相关的Bootstrap组件包装在myContainer id的标记中。

两种解决方案都有效。我的解决方案运行速度会慢一点,但会导致编码更容易,并且不易受开发人员引发的错误的影响。以下是详细信息:

应用/资产/ JavaScript的/的application.js

//= require turbolinks
//= require rails-ujs
//= require polyfill
//= require bootstrap-native
//= require bootstrap-native-turbolinks

应用/资产/ JavaScript的/自举天然-turbolinks.js

document.addEventListener('turbolinks:load', function(){
  Array.prototype.forEach.call(document.querySelectorAll('[data-spy="affix"]'), function(element){ new Affix(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-dismiss="alert"]'), function(element){ new Alert(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="buttons"]'), function(element){ new Button(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-ride="carousel"]'), function(element){ new Carousel(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="collapse"]'), function(element){ new Collapse(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="dropdown"]'), function(element){ new Dropdown(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="modal"]'), function(element){ new Modal(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="popover"]'), function(element){ new Popover(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-spy="scroll"]'), function(element){ new ScrollSpy(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="tab"]'), function(element){ new Tab(element) });
  Array.prototype.forEach.call(document.querySelectorAll('[data-toggle="tooltip"]'), function(element){ new Tooltip(element) });
},false);

<强>供应商/资产/ Javascript角/

最后,我将我的应用程序javascript标记移动到<head>并异步加载它,没有tuborlinks-eval和turbolinks-track。这会将Javascript配置为在初始页面加载时运行一次。在每个turbolinks页面访问时调用turbolinks:load侦听器,并将Bootstrap Native事件侦听器附加到DOM中的相应组件。

应用/视图/布局/ application.html.erb

...
<head>
  ...
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', 'data-turbolinks-eval': 'false', async: true %>
</head>
...

在撰写本文时,此解决方案位于生产Rails 5.1应用程序中。

顺便提一下,连接和缩小时, application.js 中列出的所有Javascript都小于20KB。相比之下,jQuery本身缩小了86KB。 Bootstrap Native可以显着缩短应用的下载时间。

答案 3 :(得分:0)

这已通过 Vue.js Laravel

进行了测试
var bsn = require('bootstrap.native/dist/bootstrap-native-v4');

document.addEventListener('turbolinks:load', () => {
    BSN.initCallback();
});